You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

83 lines
2.7 KiB

4 years ago
  1. 'use strict';
  2. var fs = require('fs');
  3. var getFieldAsFn = require('./get-field-as-fn');
  4. /**
  5. * Create a decoder for input sources using the given codec hash
  6. * @this {object} A loader or compilation
  7. * @param {Array.<object>} codecs A list of codecs, each with a `decode` function
  8. * @param {boolean} mustDecode Return an error for a source that is not decoded
  9. * @returns {function(string):string|Error} A decode function that returns an absolute path or else an Error
  10. */
  11. function decodeSourcesWith(codecs, mustDecode) {
  12. /* jshint validthis:true */
  13. var context = this;
  14. // get a list of valid decoders
  15. var candidates = [].concat(codecs)
  16. .reduce(reduceValidDecoder.bind(null, codecs), []);
  17. /**
  18. * Attempt to decode the given source path using the previously supplied codecs
  19. * @param {string} inputSource A source path from a source map
  20. * @returns {Error|string|undefined} An absolute path if decoded else an error if encountered else undefined
  21. */
  22. return function decode(inputSource) {
  23. // attempt all candidates until a match
  24. for (var i = 0, decoded = null; i < candidates.length && !decoded; i++) {
  25. // call the decoder
  26. try {
  27. decoded = candidates[i].decode.call(context, inputSource);
  28. }
  29. catch (exception) {
  30. return getNamedError(exception);
  31. }
  32. // match implies a return value
  33. if (decoded) {
  34. // abstract sources cannot be decoded, only validated
  35. if (candidates[i].abstract) {
  36. return undefined;
  37. }
  38. // non-string implies error
  39. if (typeof decoded !== 'string') {
  40. return getNamedError('Decoder returned a truthy value but it is not a string file path:\n' + decoded);
  41. }
  42. // non-existant file implies error
  43. else if (!fs.existsSync(decoded) || !fs.statSync(decoded).isFile()) {
  44. return getNamedError('Cannot find file at absolute path:\n' + decoded);
  45. }
  46. // otherwise success
  47. else {
  48. return decoded;
  49. }
  50. }
  51. }
  52. // default is undefined or error
  53. return mustDecode ? new Error('No viable decoder for source: ' + inputSource) : undefined;
  54. function getNamedError(details) {
  55. var name = candidates[i].name || '(unnamed)',
  56. message = [
  57. 'Decoding with codec: ' + name,
  58. 'Incoming source: ' + inputSource,
  59. details && (details.stack ? details.stack : details)
  60. ]
  61. .filter(Boolean)
  62. .join('\n');
  63. return new Error(message);
  64. }
  65. };
  66. }
  67. module.exports = decodeSourcesWith;
  68. function reduceValidDecoder(reduced, codec) {
  69. var decoder = getFieldAsFn('decode')(codec);
  70. return decoder ? reduced.concat(codec) : reduced;
  71. }