Skip to content
This repository has been archived by the owner on Aug 15, 2024. It is now read-only.

feat!: use ESTree directive property when searching for "use strict" #118

Merged
merged 2 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ import eslintScopeVersion from "./version.js";
function defaultOptions() {
return {
optimistic: false,
directive: false,
nodejsScope: false,
impliedStrict: false,
sourceType: "script", // one of ['script', 'module', 'commonjs']
Expand Down Expand Up @@ -115,7 +114,6 @@ function updateDeeply(target, override) {
* @param {espree.Tree} tree Abstract Syntax Tree
* @param {Object} providedOptions Options that tailor the scope analysis
* @param {boolean} [providedOptions.optimistic=false] the optimistic flag
* @param {boolean} [providedOptions.directive=false] the directive flag
* @param {boolean} [providedOptions.ignoreEval=false] whether to check 'eval()' calls
* @param {boolean} [providedOptions.nodejsScope=false] whether the whole
* script is executed under node.js environment. When enabled, escope adds
Expand Down
4 changes: 0 additions & 4 deletions lib/scope-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ class ScopeManager {
this.__declaredVariables = new WeakMap();
}

__useDirective() {
return this.__options.directive;
}

__isOptimistic() {
return this.__options.optimistic;
}
Expand Down
53 changes: 20 additions & 33 deletions lib/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,9 @@ const { Syntax } = estraverse;
* @param {Scope} scope scope
* @param {Block} block block
* @param {boolean} isMethodDefinition is method definition
* @param {boolean} useDirective use directive
* @returns {boolean} is strict scope
*/
function isStrictScope(scope, block, isMethodDefinition, useDirective) {
function isStrictScope(scope, block, isMethodDefinition) {
let body;

// When upper scope is exists and strict, inner scope is also strict.
Expand Down Expand Up @@ -82,41 +81,29 @@ function isStrictScope(scope, block, isMethodDefinition, useDirective) {
return false;
}

// Search 'use strict' directive.
if (useDirective) {
for (let i = 0, iz = body.body.length; i < iz; ++i) {
const stmt = body.body[i];
// Search for a 'use strict' directive.
for (let i = 0, iz = body.body.length; i < iz; ++i) {
const stmt = body.body[i];

if (stmt.type !== Syntax.DirectiveStatement) {
break;
}
if (stmt.raw === "\"use strict\"" || stmt.raw === "'use strict'") {
return true;
}
/*
* Check if the current statement is a directive.
* If it isn't, then we're past the directive prologue
* so stop the search because directives cannot
* appear after this point.
*
* Some parsers set `directive:null` on non-directive
* statements, so the `typeof` check is safer than
* checking for property existence.
*/
if (typeof stmt.directive !== "string") {
break;
}
} else {
for (let i = 0, iz = body.body.length; i < iz; ++i) {
const stmt = body.body[i];

if (stmt.type !== Syntax.ExpressionStatement) {
break;
}
const expr = stmt.expression;

if (expr.type !== Syntax.Literal || typeof expr.value !== "string") {
break;
}
if (expr.raw !== null && expr.raw !== undefined) {
if (expr.raw === "\"use strict\"" || expr.raw === "'use strict'") {
return true;
}
} else {
if (expr.value === "use strict") {
return true;
}
}
if (stmt.directive === "use strict") {
return true;
}
}

return false;
}

Expand Down Expand Up @@ -264,7 +251,7 @@ class Scope {
* @member {boolean} Scope#isStrict
*/
this.isStrict = scopeManager.isStrictModeSupported()
? isStrictScope(this, block, isMethodDefinition, scopeManager.__useDirective())
? isStrictScope(this, block, isMethodDefinition)
: false;

/**
Expand Down
180 changes: 180 additions & 0 deletions tests/use-strict.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,184 @@ describe("'use strict' directives", () => {
assertIsStrictRecursively(globalScope.childScopes[1], false); // function e() { ... }
});
});

it("can be with single quotes at the top level", () => {
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
const ast = espree.parse(`
'use strict';
`, { ecmaVersion, range: true });

const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });

assert.strictEqual(globalScope.isStrict, true);
});
});

it("can be without the semicolon at the top level", () => {
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
const ast = espree.parse(`
"use strict"
foo()
`, { ecmaVersion, range: true });

const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });

assert.strictEqual(globalScope.isStrict, true);
});
});

it("can be anywhere in the directive prologue at the top level", () => {
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
const ast = espree.parse(`
"foo";
"use strict";
`, { ecmaVersion, range: true });

const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });

assert.strictEqual(globalScope.isStrict, true);
});
});

it("cannot be after the directive prologue at the top level", () => {
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
const ast = espree.parse(`
foo();
"use strict";
`, { ecmaVersion, range: true });

const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });

assert.strictEqual(globalScope.isStrict, false);
});
});

it("cannot contain escapes at the top level", () => {
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
const ast = espree.parse(`
"use \\strict";
`, { ecmaVersion, range: true });

const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });

assert.strictEqual(globalScope.isStrict, false);
});
});

it("cannot be parenthesized at the top level", () => {
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
const ast = espree.parse(`
("use strict");
`, { ecmaVersion, range: true });

const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });

assert.strictEqual(globalScope.isStrict, false);
});
});

it("can be with single quotes in a function", () => {
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
const ast = espree.parse(`
function foo() {
'use strict';
}
`, { ecmaVersion, range: true });

const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });

assert.strictEqual(globalScope.isStrict, false);
assert.strictEqual(globalScope.childScopes.length, 1);
assert.strictEqual(globalScope.childScopes[0].type, "function");
assert.strictEqual(globalScope.childScopes[0].isStrict, true);
});
});

it("can be without the semicolon in a function", () => {
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
const ast = espree.parse(`
function foo() {
"use strict"
bar()
}
`, { ecmaVersion, range: true });

const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });

assert.strictEqual(globalScope.isStrict, false);
assert.strictEqual(globalScope.childScopes.length, 1);
assert.strictEqual(globalScope.childScopes[0].type, "function");
assert.strictEqual(globalScope.childScopes[0].isStrict, true);
});
});

it("can be anywhere in the directive prologue in a function", () => {
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
const ast = espree.parse(`
function foo() {
"foo";
"use strict";
}
`, { ecmaVersion, range: true });

const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });

assert.strictEqual(globalScope.isStrict, false);
assert.strictEqual(globalScope.childScopes.length, 1);
assert.strictEqual(globalScope.childScopes[0].type, "function");
assert.strictEqual(globalScope.childScopes[0].isStrict, true);
});
});

it("cannot be after the directive prologue in a function", () => {
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
const ast = espree.parse(`
function foo() {
bar();
"use strict";
}
`, { ecmaVersion, range: true });

const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });

assert.strictEqual(globalScope.isStrict, false);
assert.strictEqual(globalScope.childScopes.length, 1);
assert.strictEqual(globalScope.childScopes[0].type, "function");
assert.strictEqual(globalScope.childScopes[0].isStrict, false);
});
});

it("cannot contain escapes in a function", () => {
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
const ast = espree.parse(`
function foo() {
"use \\strict";
}
`, { ecmaVersion, range: true });

const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });

assert.strictEqual(globalScope.isStrict, false);
assert.strictEqual(globalScope.childScopes.length, 1);
assert.strictEqual(globalScope.childScopes[0].type, "function");
assert.strictEqual(globalScope.childScopes[0].isStrict, false);
});
});

it("cannot be parenthesized in a function", () => {
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
const ast = espree.parse(`
function foo() {
("use strict");
}
`, { ecmaVersion, range: true });

const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });

assert.strictEqual(globalScope.isStrict, false);
assert.strictEqual(globalScope.childScopes.length, 1);
assert.strictEqual(globalScope.childScopes[0].type, "function");
assert.strictEqual(globalScope.childScopes[0].isStrict, false);
});
});
});