diff --git a/packages/babel-generator/src/generators/base.js b/packages/babel-generator/src/generators/base.js index 49a1cb2c88..cb98733e55 100644 --- a/packages/babel-generator/src/generators/base.js +++ b/packages/babel-generator/src/generators/base.js @@ -50,8 +50,35 @@ export function Directive(node: Object) { this.semicolon(); } +// These regexes match an even number of \ followed by a quote +const unescapedSingleQuoteRE = /(?:^|[^\\])(?:\\\\)*'/; +const unescapedDoubleQuoteRE = /(?:^|[^\\])(?:\\\\)*"/; + +export function DirectiveLiteral(node: Object) { + const raw = this.getPossibleRaw(node); + if (raw != null) { + this.token(raw); + return; + } + + const { value } = node; + + // NOTE: In directives we can't change escapings, + // because they change the behavior. + // e.g. "us\x65 string" (\x65 is e) is not a "use strict" directive. + + if (!unescapedDoubleQuoteRE.test(value)) { + this.token(`"${value}"`); + } else if (!unescapedSingleQuoteRE.test(value)) { + this.token(`'${value}'`); + } else { + throw new Error( + "Malformed AST: it is not possible to print a directive containing" + + " both unescaped single and double quotes.", + ); + } +} + export function InterpreterDirective(node: Object) { this.token(`#!${node.value}\n`); } - -export { StringLiteral as DirectiveLiteral } from "./types"; diff --git a/packages/babel-generator/test/fixtures/escapes/jsonEscape/input.js b/packages/babel-generator/test/fixtures/escapes/jsonEscape/input.js index 2305ada28e..b999a74fa0 100644 --- a/packages/babel-generator/test/fixtures/escapes/jsonEscape/input.js +++ b/packages/babel-generator/test/fixtures/escapes/jsonEscape/input.js @@ -1 +1,2 @@ +0; // Not a directive "©"; diff --git a/packages/babel-generator/test/fixtures/escapes/jsonEscape/output.js b/packages/babel-generator/test/fixtures/escapes/jsonEscape/output.js index 354db9cc77..4da42cb397 100644 --- a/packages/babel-generator/test/fixtures/escapes/jsonEscape/output.js +++ b/packages/babel-generator/test/fixtures/escapes/jsonEscape/output.js @@ -1 +1,2 @@ +0;// Not a directive "\u00A9"; \ No newline at end of file diff --git a/packages/babel-generator/test/index.js b/packages/babel-generator/test/index.js index f79b964d0a..bf2fef0dfd 100644 --- a/packages/babel-generator/test/index.js +++ b/packages/babel-generator/test/index.js @@ -384,6 +384,48 @@ describe("programmatic generation", function() { [key: any]: number }`); }); + + describe("directives", function() { + it("preserves escapes", function() { + const directive = t.directive( + t.directiveLiteral(String.raw`us\x65 strict`), + ); + const output = generate(directive).code; + + expect(output).toBe(String.raw`"us\x65 strict";`); + }); + + it("preserves escapes in minified output", function() { + // https://github.com/babel/babel/issues/4767 + + const directive = t.directive(t.directiveLiteral(String.raw`foo\n\t\r`)); + const output = generate(directive, { minified: true }).code; + + expect(output).toBe(String.raw`"foo\n\t\r";`); + }); + + it("unescaped single quote", function() { + const directive = t.directive(t.directiveLiteral(String.raw`'\'\"`)); + const output = generate(directive).code; + + expect(output).toBe(String.raw`"'\'\"";`); + }); + + it("unescaped double quote", function() { + const directive = t.directive(t.directiveLiteral(String.raw`"\'\"`)); + const output = generate(directive).code; + + expect(output).toBe(String.raw`'"\'\"';`); + }); + + it("unescaped single and double quotes together throw", function() { + const directive = t.directive(t.directiveLiteral(String.raw`'"`)); + + expect(() => { + generate(directive); + }).toThrow(); + }); + }); }); describe("CodeGenerator", function() {