247 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			247 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| var fs = require('fs');
 | |
| var path = require('path');
 | |
| 
 | |
| var isAllowedResource = require('./is-allowed-resource');
 | |
| var matchDataUri = require('./match-data-uri');
 | |
| var rebaseLocalMap = require('./rebase-local-map');
 | |
| var rebaseRemoteMap = require('./rebase-remote-map');
 | |
| 
 | |
| var Token = require('../tokenizer/token');
 | |
| var hasProtocol = require('../utils/has-protocol');
 | |
| var isDataUriResource = require('../utils/is-data-uri-resource');
 | |
| var isRemoteResource = require('../utils/is-remote-resource');
 | |
| 
 | |
| var MAP_MARKER_PATTERN = /^\/\*# sourceMappingURL=(\S+) \*\/$/;
 | |
| 
 | |
| function applySourceMaps(tokens, context, callback) {
 | |
|   var applyContext = {
 | |
|     callback: callback,
 | |
|     fetch: context.options.fetch,
 | |
|     index: 0,
 | |
|     inline: context.options.inline,
 | |
|     inlineRequest: context.options.inlineRequest,
 | |
|     inlineTimeout: context.options.inlineTimeout,
 | |
|     inputSourceMapTracker: context.inputSourceMapTracker,
 | |
|     localOnly: context.localOnly,
 | |
|     processedTokens: [],
 | |
|     rebaseTo: context.options.rebaseTo,
 | |
|     sourceTokens: tokens,
 | |
|     warnings: context.warnings
 | |
|   };
 | |
| 
 | |
|   return context.options.sourceMap && tokens.length > 0
 | |
|     ? doApplySourceMaps(applyContext)
 | |
|     : callback(tokens);
 | |
| }
 | |
| 
 | |
| function doApplySourceMaps(applyContext) {
 | |
|   var singleSourceTokens = [];
 | |
|   var lastSource = findTokenSource(applyContext.sourceTokens[0]);
 | |
|   var source;
 | |
|   var token;
 | |
|   var l;
 | |
| 
 | |
|   for (l = applyContext.sourceTokens.length; applyContext.index < l; applyContext.index++) {
 | |
|     token = applyContext.sourceTokens[applyContext.index];
 | |
|     source = findTokenSource(token);
 | |
| 
 | |
|     if (source != lastSource) {
 | |
|       singleSourceTokens = [];
 | |
|       lastSource = source;
 | |
|     }
 | |
| 
 | |
|     singleSourceTokens.push(token);
 | |
|     applyContext.processedTokens.push(token);
 | |
| 
 | |
|     if (token[0] == Token.COMMENT && MAP_MARKER_PATTERN.test(token[1])) {
 | |
|       return fetchAndApplySourceMap(token[1], source, singleSourceTokens, applyContext);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return applyContext.callback(applyContext.processedTokens);
 | |
| }
 | |
| 
 | |
| function findTokenSource(token) {
 | |
|   var scope;
 | |
|   var metadata;
 | |
| 
 | |
|   if (token[0] == Token.AT_RULE || token[0] == Token.COMMENT || token[0] == Token.RAW) {
 | |
|     metadata = token[2][0];
 | |
|   } else {
 | |
|     scope = token[1][0];
 | |
|     metadata = scope[2][0];
 | |
|   }
 | |
| 
 | |
|   return metadata[2];
 | |
| }
 | |
| 
 | |
| function fetchAndApplySourceMap(sourceMapComment, source, singleSourceTokens, applyContext) {
 | |
|   return extractInputSourceMapFrom(sourceMapComment, applyContext, function(inputSourceMap) {
 | |
|     if (inputSourceMap) {
 | |
|       applyContext.inputSourceMapTracker.track(source, inputSourceMap);
 | |
|       applySourceMapRecursively(singleSourceTokens, applyContext.inputSourceMapTracker);
 | |
|     }
 | |
| 
 | |
|     applyContext.index++;
 | |
|     return doApplySourceMaps(applyContext);
 | |
|   });
 | |
| }
 | |
| 
 | |
| function extractInputSourceMapFrom(sourceMapComment, applyContext, whenSourceMapReady) {
 | |
|   var uri = MAP_MARKER_PATTERN.exec(sourceMapComment)[1];
 | |
|   var absoluteUri;
 | |
|   var sourceMap;
 | |
|   var rebasedMap;
 | |
| 
 | |
|   if (isDataUriResource(uri)) {
 | |
|     sourceMap = extractInputSourceMapFromDataUri(uri);
 | |
|     return whenSourceMapReady(sourceMap);
 | |
|   } if (isRemoteResource(uri)) {
 | |
|     return loadInputSourceMapFromRemoteUri(uri, applyContext, function(sourceMap) {
 | |
|       var parsedMap;
 | |
| 
 | |
|       if (sourceMap) {
 | |
|         parsedMap = JSON.parse(sourceMap);
 | |
|         rebasedMap = rebaseRemoteMap(parsedMap, uri);
 | |
|         whenSourceMapReady(rebasedMap);
 | |
|       } else {
 | |
|         whenSourceMapReady(null);
 | |
|       }
 | |
|     });
 | |
|   }
 | |
|   // at this point `uri` is already rebased, see lib/reader/rebase.js#rebaseSourceMapComment
 | |
|   // it is rebased to be consistent with rebasing other URIs
 | |
|   // however here we need to resolve it back to read it from disk
 | |
|   absoluteUri = path.resolve(applyContext.rebaseTo, uri);
 | |
|   sourceMap = loadInputSourceMapFromLocalUri(absoluteUri, applyContext);
 | |
| 
 | |
|   if (sourceMap) {
 | |
|     rebasedMap = rebaseLocalMap(sourceMap, absoluteUri, applyContext.rebaseTo);
 | |
|     return whenSourceMapReady(rebasedMap);
 | |
|   }
 | |
|   return whenSourceMapReady(null);
 | |
| }
 | |
| 
 | |
| function extractInputSourceMapFromDataUri(uri) {
 | |
|   var dataUriMatch = matchDataUri(uri);
 | |
|   var charset = dataUriMatch[2] ? dataUriMatch[2].split(/[=;]/)[2] : 'us-ascii';
 | |
|   var encoding = dataUriMatch[3] ? dataUriMatch[3].split(';')[1] : 'utf8';
 | |
|   var data = encoding == 'utf8' ? global.unescape(dataUriMatch[4]) : dataUriMatch[4];
 | |
| 
 | |
|   var buffer = Buffer.from(data, encoding);
 | |
|   buffer.charset = charset;
 | |
| 
 | |
|   return JSON.parse(buffer.toString());
 | |
| }
 | |
| 
 | |
| function loadInputSourceMapFromRemoteUri(uri, applyContext, whenLoaded) {
 | |
|   var isAllowed = isAllowedResource(uri, true, applyContext.inline);
 | |
|   var isRuntimeResource = !hasProtocol(uri);
 | |
| 
 | |
|   if (applyContext.localOnly) {
 | |
|     applyContext.warnings.push('Cannot fetch remote resource from "' + uri + '" as no callback given.');
 | |
|     return whenLoaded(null);
 | |
|   } if (isRuntimeResource) {
 | |
|     applyContext.warnings.push('Cannot fetch "' + uri + '" as no protocol given.');
 | |
|     return whenLoaded(null);
 | |
|   } if (!isAllowed) {
 | |
|     applyContext.warnings.push('Cannot fetch "' + uri + '" as resource is not allowed.');
 | |
|     return whenLoaded(null);
 | |
|   }
 | |
| 
 | |
|   applyContext.fetch(uri, applyContext.inlineRequest, applyContext.inlineTimeout, function(error, body) {
 | |
|     if (error) {
 | |
|       applyContext.warnings.push('Missing source map at "' + uri + '" - ' + error);
 | |
|       return whenLoaded(null);
 | |
|     }
 | |
| 
 | |
|     whenLoaded(body);
 | |
|   });
 | |
| }
 | |
| 
 | |
| function loadInputSourceMapFromLocalUri(uri, applyContext) {
 | |
|   var isAllowed = isAllowedResource(uri, false, applyContext.inline);
 | |
|   var sourceMap;
 | |
| 
 | |
|   if (!fs.existsSync(uri) || !fs.statSync(uri).isFile()) {
 | |
|     applyContext.warnings.push('Ignoring local source map at "' + uri + '" as resource is missing.');
 | |
|     return null;
 | |
|   } if (!isAllowed) {
 | |
|     applyContext.warnings.push('Cannot fetch "' + uri + '" as resource is not allowed.');
 | |
|     return null;
 | |
|   } if (!fs.statSync(uri).size) {
 | |
|     applyContext.warnings.push('Cannot fetch "' + uri + '" as resource is empty.');
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   sourceMap = fs.readFileSync(uri, 'utf-8');
 | |
|   return JSON.parse(sourceMap);
 | |
| }
 | |
| 
 | |
| function applySourceMapRecursively(tokens, inputSourceMapTracker) {
 | |
|   var token;
 | |
|   var i, l;
 | |
| 
 | |
|   for (i = 0, l = tokens.length; i < l; i++) {
 | |
|     token = tokens[i];
 | |
| 
 | |
|     switch (token[0]) {
 | |
|     case Token.AT_RULE:
 | |
|       applySourceMapTo(token, inputSourceMapTracker);
 | |
|       break;
 | |
|     case Token.AT_RULE_BLOCK:
 | |
|       applySourceMapRecursively(token[1], inputSourceMapTracker);
 | |
|       applySourceMapRecursively(token[2], inputSourceMapTracker);
 | |
|       break;
 | |
|     case Token.AT_RULE_BLOCK_SCOPE:
 | |
|       applySourceMapTo(token, inputSourceMapTracker);
 | |
|       break;
 | |
|     case Token.NESTED_BLOCK:
 | |
|       applySourceMapRecursively(token[1], inputSourceMapTracker);
 | |
|       applySourceMapRecursively(token[2], inputSourceMapTracker);
 | |
|       break;
 | |
|     case Token.NESTED_BLOCK_SCOPE:
 | |
|       applySourceMapTo(token, inputSourceMapTracker);
 | |
|       break;
 | |
|     case Token.COMMENT:
 | |
|       applySourceMapTo(token, inputSourceMapTracker);
 | |
|       break;
 | |
|     case Token.PROPERTY:
 | |
|       applySourceMapRecursively(token, inputSourceMapTracker);
 | |
|       break;
 | |
|     case Token.PROPERTY_BLOCK:
 | |
|       applySourceMapRecursively(token[1], inputSourceMapTracker);
 | |
|       break;
 | |
|     case Token.PROPERTY_NAME:
 | |
|       applySourceMapTo(token, inputSourceMapTracker);
 | |
|       break;
 | |
|     case Token.PROPERTY_VALUE:
 | |
|       applySourceMapTo(token, inputSourceMapTracker);
 | |
|       break;
 | |
|     case Token.RULE:
 | |
|       applySourceMapRecursively(token[1], inputSourceMapTracker);
 | |
|       applySourceMapRecursively(token[2], inputSourceMapTracker);
 | |
|       break;
 | |
|     case Token.RULE_SCOPE:
 | |
|       applySourceMapTo(token, inputSourceMapTracker);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return tokens;
 | |
| }
 | |
| 
 | |
| function applySourceMapTo(token, inputSourceMapTracker) {
 | |
|   var value = token[1];
 | |
|   var metadata = token[2];
 | |
|   var newMetadata = [];
 | |
|   var i, l;
 | |
| 
 | |
|   for (i = 0, l = metadata.length; i < l; i++) {
 | |
|     newMetadata.push(inputSourceMapTracker.originalPositionFor(metadata[i], value.length));
 | |
|   }
 | |
| 
 | |
|   token[2] = newMetadata;
 | |
| }
 | |
| 
 | |
| module.exports = applySourceMaps;
 |