diff --git a/packages/babel-plugin-transform-classes/src/transformClass.js b/packages/babel-plugin-transform-classes/src/transformClass.js index b4283eedf8..b6276f1444 100644 --- a/packages/babel-plugin-transform-classes/src/transformClass.js +++ b/packages/babel-plugin-transform-classes/src/transformClass.js @@ -688,26 +688,9 @@ export default function transformClass( classState.staticPropBody.map(fn => fn(t.cloneNode(classState.classRef))), ); - const strictParent = path.findParent(path => { - if (path.isProgram() && path.node.sourceType === "module") { - return true; - } - - if (path.isClassBody()) { - return true; - } - - if (!path.isProgram() && !path.isBlockStatement()) { - return false; - } - - return path.node.directives.some( - directive => directive.value.value === "use strict", - ); - }); - + const isStrict = path.isInStrictMode(); let constructorOnly = classState.classId && body.length === 1; - if (constructorOnly && !strictParent) { + if (constructorOnly && !isStrict) { for (const param of classState.construct.params) { // It's illegal to put a use strict directive into the body of a function // with non-simple parameters for some reason. So, we have to use a strict @@ -720,7 +703,7 @@ export default function transformClass( } const directives = constructorOnly ? body[0].body.directives : []; - if (!strictParent) { + if (!isStrict) { directives.push(t.directive(t.directiveLiteral("use strict"))); } diff --git a/packages/babel-traverse/src/path/introspection.js b/packages/babel-traverse/src/path/introspection.js index f3053343fe..2ab558d5aa 100644 --- a/packages/babel-traverse/src/path/introspection.js +++ b/packages/babel-traverse/src/path/introspection.js @@ -439,3 +439,33 @@ export function isConstantExpression() { return false; } + +export function isInStrictMode() { + const start = this.isProgram() ? this : this.parentPath; + + const strictParent = start.find(path => { + if (path.isProgram({ sourceType: "module" })) return true; + + if (path.isClass()) return true; + + if (!path.isProgram() && !path.isFunction()) return false; + + if ( + path.isArrowFunctionExpression() && + !path.get("body").isBlockStatement() + ) { + return false; + } + + let { node } = path; + if (path.isFunction()) node = node.body; + + for (const directive of node.directives) { + if (directive.value.value === "use strict") { + return true; + } + } + }); + + return !!strictParent; +} diff --git a/packages/babel-traverse/test/introspection.js b/packages/babel-traverse/test/introspection.js new file mode 100644 index 0000000000..bdb953d102 --- /dev/null +++ b/packages/babel-traverse/test/introspection.js @@ -0,0 +1,103 @@ +import traverse from "../lib"; +import { parse } from "babylon"; + +function getPath(code, options = { sourceType: "script" }) { + const ast = parse(code, options); + let path; + traverse(ast, { + Program: function(_path) { + path = _path; + _path.stop(); + }, + }); + return path; +} + +describe("path/introspection", function() { + describe("isInStrictMode", function() { + describe("classes", function() { + it("returns parent's strictness for class", function() { + let program = getPath("class Test extends Super {}"); + let klass = program.get("body.0"); + expect(klass.isInStrictMode()).toBeFalsy(); + + program = getPath(`"use strict"; class Test extends Super {}`); + klass = program.get("body.0"); + expect(klass.isInStrictMode()).toBeTruthy(); + }); + + it("returns true for class id", function() { + const program = getPath("class Test extends Super {}"); + const id = program.get("body.0.id"); + expect(id.isInStrictMode()).toBeTruthy(); + }); + + it("returns true for superClass", function() { + const program = getPath("class Test extends Super {}"); + const superClass = program.get("body.0.superClass"); + expect(superClass.isInStrictMode()).toBeTruthy(); + }); + + it("returns true for method", function() { + const program = getPath("class Test { test() {} }"); + const method = program.get("body.0.body.body.0"); + expect(method.isInStrictMode()).toBeTruthy(); + }); + }); + + describe("program", function() { + describe("when script", function() { + it("returns true when strict", function() { + let program = getPath(`test;`); + expect(program.isInStrictMode()).toBeFalsy(); + + program = getPath(`"use strict";`); + expect(program.isInStrictMode()).toBeTruthy(); + }); + }); + + describe("when module", function() { + it("returns true", function() { + const program = getPath(`test;`, { sourceType: "module" }); + expect(program.isInStrictMode()).toBeTruthy(); + }); + }); + }); + + describe("function", function() { + it("returns parent's strictness for function", function() { + let program = getPath("function test() {}"); + let fn = program.get("body.0"); + expect(fn.isInStrictMode()).toBeFalsy(); + + program = getPath(`function test() {"use strict";}`); + fn = program.get("body.0"); + expect(fn.isInStrictMode()).toBeFalsy(); + + program = getPath(`"use strict"; function test() {}`); + fn = program.get("body.0"); + expect(fn.isInStrictMode()).toBeTruthy(); + }); + + it("returns function's strictness for id", function() { + let program = getPath("function test(a) {}"); + let id = program.get("body.0.id"); + expect(id.isInStrictMode()).toBeFalsy(); + + program = getPath(`function test(a) {"use strict";}`); + id = program.get("body.0.id"); + expect(id.isInStrictMode()).toBeTruthy(); + }); + + it("returns function's strictness for parameters", function() { + let program = getPath("function test(a) {}"); + let param = program.get("body.0.params.0"); + expect(param.isInStrictMode()).toBeFalsy(); + + program = getPath(`function test(a) {"use strict";}`); + param = program.get("body.0.params.0"); + expect(param.isInStrictMode()).toBeTruthy(); + }); + }); + }); +});