decode-sources-with.js 2.4 KB

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