diff --git a/lib/third-party-libs.js.flow b/lib/third-party-libs.js.flow index c21a2797acfe..612dc5130e39 100644 --- a/lib/third-party-libs.js.flow +++ b/lib/third-party-libs.js.flow @@ -181,7 +181,7 @@ declare module "convert-source-map" { fromJSON(str: string): Converter, fromBase64(str: string): Converter, fromComment(str: string): Converter, - fromMapFileComment(str: string): Converter, + fromMapFileComment(str: string, dir: string): Converter, fromSource(str: string): Converter, fromMapFileSource(str: string, dir: string): Converter, removeComments(str: string): string, diff --git a/packages/babel-core/src/transformation/normalize-file.js b/packages/babel-core/src/transformation/normalize-file.js index 1df42ad763b2..0bc88c8a0c1b 100644 --- a/packages/babel-core/src/transformation/normalize-file.js +++ b/packages/babel-core/src/transformation/normalize-file.js @@ -27,6 +27,20 @@ export default function normalizeFile( ): File { code = `${code || ""}`; + if (ast) { + if (ast.type === "Program") { + ast = t.file(ast, [], []); + } else if (ast.type !== "File") { + throw new Error("AST root must be a Program or File node"); + } + ast = cloneDeep(ast); + } else { + // The parser's AST types aren't fully compatible with the types generated + // by the logic in babel-types. + // $FlowFixMe + ast = parser(pluginPasses, options, code); + } + let inputMap = null; if (options.inputSourceMap !== false) { // If an explicit object is passed in, it overrides the processing of @@ -36,54 +50,33 @@ export default function normalizeFile( } if (!inputMap) { - try { - inputMap = convertSourceMap.fromSource(code); - - if (inputMap) { - code = convertSourceMap.removeComments(code); + const lastComment = extractComments(INLINE_SOURCEMAP_REGEX, ast); + if (lastComment) { + try { + inputMap = convertSourceMap.fromComment(lastComment); + } catch (err) { + debug("discarding unknown inline input sourcemap", err); } - } catch (err) { - debug("discarding unknown inline input sourcemap", err); - code = convertSourceMap.removeComments(code); } } if (!inputMap) { - if (typeof options.filename === "string") { + const lastComment = extractComments(EXTERNAL_SOURCEMAP_REGEX, ast); + if (typeof options.filename === "string" && lastComment) { try { - inputMap = convertSourceMap.fromMapFileSource( - code, + inputMap = convertSourceMap.fromMapFileComment( + lastComment, path.dirname(options.filename), ); - - if (inputMap) { - code = convertSourceMap.removeMapFileComments(code); - } } catch (err) { debug("discarding unknown file input sourcemap", err); - code = convertSourceMap.removeMapFileComments(code); } - } else { + } else if (lastComment) { debug("discarding un-loadable file input sourcemap"); - code = convertSourceMap.removeMapFileComments(code); } } } - if (ast) { - if (ast.type === "Program") { - ast = t.file(ast, [], []); - } else if (ast.type !== "File") { - throw new Error("AST root must be a Program or File node"); - } - ast = cloneDeep(ast); - } else { - // The parser's AST types aren't fully compatible with the types generated - // by the logic in babel-types. - // $FlowFixMe - ast = parser(pluginPasses, options, code); - } - return new File(options, { code, ast, @@ -156,3 +149,48 @@ function parser( throw err; } } + +// These regexps are copied from the convert-source-map package, +// but without // or /* at the beginning of the comment. + +// eslint-disable-next-line max-len +const INLINE_SOURCEMAP_REGEX = /^[@#]\s+sourceMappingURL=data:(?:application|text)\/json;(?:charset[:=]\S+?;)?base64,(?:.*)$/; +const EXTERNAL_SOURCEMAP_REGEX = /^[@#][ \t]+sourceMappingURL=([^\s'"`]+?)[ \t]*$/; + +function extractCommentsFromList(regex, comments, lastComment) { + if (comments) { + comments = comments.filter(({ value }) => { + if (regex.test(value)) { + lastComment = value; + return false; + } + return true; + }); + } + return [comments, lastComment]; +} + +function extractComments(regex, ast) { + let lastComment = null; + t.traverseFast(ast, node => { + // $FlowIgnore destructuring with expressions is not supported + [node.leadingComments, lastComment] = extractCommentsFromList( + regex, + node.leadingComments, + lastComment, + ); + // $FlowIgnore destructuring with expressions is not supported + [node.innerComments, lastComment] = extractCommentsFromList( + regex, + node.innerComments, + lastComment, + ); + // $FlowIgnore destructuring with expressions is not supported + [node.trailingComments, lastComment] = extractCommentsFromList( + regex, + node.trailingComments, + lastComment, + ); + }); + return lastComment; +} diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/comment-inside-string/input.js b/packages/babel-core/test/fixtures/transformation/source-maps/comment-inside-string/input.js new file mode 100644 index 000000000000..b883f5d7b9a4 --- /dev/null +++ b/packages/babel-core/test/fixtures/transformation/source-maps/comment-inside-string/input.js @@ -0,0 +1,8 @@ +// https://github.com/babel/babel/issues/9790 +const comment = `//# sourceMappingURL=${path.basename( + sourceMapFilename +)}` + +// https://github.com/babel/babel/issues/9956 +this.shadowRoot.innerHTML = ``; diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/comment-inside-string/options.json b/packages/babel-core/test/fixtures/transformation/source-maps/comment-inside-string/options.json new file mode 100644 index 000000000000..0e6084f210d8 --- /dev/null +++ b/packages/babel-core/test/fixtures/transformation/source-maps/comment-inside-string/options.json @@ -0,0 +1,3 @@ +{ + "inputSourceMap": true +} diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/comment-inside-string/output.js b/packages/babel-core/test/fixtures/transformation/source-maps/comment-inside-string/output.js new file mode 100644 index 000000000000..9188ec5abc3b --- /dev/null +++ b/packages/babel-core/test/fixtures/transformation/source-maps/comment-inside-string/output.js @@ -0,0 +1,5 @@ +// https://github.com/babel/babel/issues/9790 +const comment = `//# sourceMappingURL=${path.basename(sourceMapFilename)}`; // https://github.com/babel/babel/issues/9956 + +this.shadowRoot.innerHTML = ``; diff --git a/packages/babel-types/src/traverse/traverseFast.js b/packages/babel-types/src/traverse/traverseFast.js index 580628343cfb..e262c10ad47d 100644 --- a/packages/babel-types/src/traverse/traverseFast.js +++ b/packages/babel-types/src/traverse/traverseFast.js @@ -7,7 +7,7 @@ import { VISITOR_KEYS } from "../definitions"; */ export default function traverseFast( node: Object, - enter: (node: Node, opts?: Object) => void, + enter: (node: BabelNode, opts?: Object) => void, opts?: Object, ): void { if (!node) return;