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 1 commit
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
75 changes: 75 additions & 0 deletions tests/use-strict.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,79 @@ describe("'use strict' directives", () => {
assertIsStrictRecursively(globalScope.childScopes[1], false); // function e() { ... }
});
});

it("can be with single quotes", () => {
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", () => {
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", () => {
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", () => {
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", () => {
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", () => {
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);
});
});
});