Skip to content
This repository was archived by the owner on Jun 23, 2025. It is now read-only.

Commit 23fe81f

Browse files
authored
feat!: use ESTree directive property when searching for "use strict" (#118)
* feat!: use ESTree `directive` property when searching for `"use strict"` Fixes #117 * add tests with functions
1 parent 42ef7a9 commit 23fe81f

File tree

4 files changed

+200
-39
lines changed

4 files changed

+200
-39
lines changed

lib/index.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ import eslintScopeVersion from "./version.js";
6363
function defaultOptions() {
6464
return {
6565
optimistic: false,
66-
directive: false,
6766
nodejsScope: false,
6867
impliedStrict: false,
6968
sourceType: "script", // one of ['script', 'module', 'commonjs']
@@ -115,7 +114,6 @@ function updateDeeply(target, override) {
115114
* @param {espree.Tree} tree Abstract Syntax Tree
116115
* @param {Object} providedOptions Options that tailor the scope analysis
117116
* @param {boolean} [providedOptions.optimistic=false] the optimistic flag
118-
* @param {boolean} [providedOptions.directive=false] the directive flag
119117
* @param {boolean} [providedOptions.ignoreEval=false] whether to check 'eval()' calls
120118
* @param {boolean} [providedOptions.nodejsScope=false] whether the whole
121119
* script is executed under node.js environment. When enabled, escope adds

lib/scope-manager.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,6 @@ class ScopeManager {
5353
this.__declaredVariables = new WeakMap();
5454
}
5555

56-
__useDirective() {
57-
return this.__options.directive;
58-
}
59-
6056
__isOptimistic() {
6157
return this.__options.optimistic;
6258
}

lib/scope.js

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,9 @@ const { Syntax } = estraverse;
3939
* @param {Scope} scope scope
4040
* @param {Block} block block
4141
* @param {boolean} isMethodDefinition is method definition
42-
* @param {boolean} useDirective use directive
4342
* @returns {boolean} is strict scope
4443
*/
45-
function isStrictScope(scope, block, isMethodDefinition, useDirective) {
44+
function isStrictScope(scope, block, isMethodDefinition) {
4645
let body;
4746

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

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

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

101-
if (stmt.type !== Syntax.ExpressionStatement) {
102-
break;
103-
}
104-
const expr = stmt.expression;
105-
106-
if (expr.type !== Syntax.Literal || typeof expr.value !== "string") {
107-
break;
108-
}
109-
if (expr.raw !== null && expr.raw !== undefined) {
110-
if (expr.raw === "\"use strict\"" || expr.raw === "'use strict'") {
111-
return true;
112-
}
113-
} else {
114-
if (expr.value === "use strict") {
115-
return true;
116-
}
117-
}
102+
if (stmt.directive === "use strict") {
103+
return true;
118104
}
119105
}
106+
120107
return false;
121108
}
122109

@@ -264,7 +251,7 @@ class Scope {
264251
* @member {boolean} Scope#isStrict
265252
*/
266253
this.isStrict = scopeManager.isStrictModeSupported()
267-
? isStrictScope(this, block, isMethodDefinition, scopeManager.__useDirective())
254+
? isStrictScope(this, block, isMethodDefinition)
268255
: false;
269256

270257
/**

tests/use-strict.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,184 @@ describe("'use strict' directives", () => {
9898
assertIsStrictRecursively(globalScope.childScopes[1], false); // function e() { ... }
9999
});
100100
});
101+
102+
it("can be with single quotes at the top level", () => {
103+
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
104+
const ast = espree.parse(`
105+
'use strict';
106+
`, { ecmaVersion, range: true });
107+
108+
const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });
109+
110+
assert.strictEqual(globalScope.isStrict, true);
111+
});
112+
});
113+
114+
it("can be without the semicolon at the top level", () => {
115+
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
116+
const ast = espree.parse(`
117+
"use strict"
118+
foo()
119+
`, { ecmaVersion, range: true });
120+
121+
const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });
122+
123+
assert.strictEqual(globalScope.isStrict, true);
124+
});
125+
});
126+
127+
it("can be anywhere in the directive prologue at the top level", () => {
128+
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
129+
const ast = espree.parse(`
130+
"foo";
131+
"use strict";
132+
`, { ecmaVersion, range: true });
133+
134+
const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });
135+
136+
assert.strictEqual(globalScope.isStrict, true);
137+
});
138+
});
139+
140+
it("cannot be after the directive prologue at the top level", () => {
141+
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
142+
const ast = espree.parse(`
143+
foo();
144+
"use strict";
145+
`, { ecmaVersion, range: true });
146+
147+
const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });
148+
149+
assert.strictEqual(globalScope.isStrict, false);
150+
});
151+
});
152+
153+
it("cannot contain escapes at the top level", () => {
154+
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
155+
const ast = espree.parse(`
156+
"use \\strict";
157+
`, { ecmaVersion, range: true });
158+
159+
const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });
160+
161+
assert.strictEqual(globalScope.isStrict, false);
162+
});
163+
});
164+
165+
it("cannot be parenthesized at the top level", () => {
166+
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
167+
const ast = espree.parse(`
168+
("use strict");
169+
`, { ecmaVersion, range: true });
170+
171+
const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });
172+
173+
assert.strictEqual(globalScope.isStrict, false);
174+
});
175+
});
176+
177+
it("can be with single quotes in a function", () => {
178+
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
179+
const ast = espree.parse(`
180+
function foo() {
181+
'use strict';
182+
}
183+
`, { ecmaVersion, range: true });
184+
185+
const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });
186+
187+
assert.strictEqual(globalScope.isStrict, false);
188+
assert.strictEqual(globalScope.childScopes.length, 1);
189+
assert.strictEqual(globalScope.childScopes[0].type, "function");
190+
assert.strictEqual(globalScope.childScopes[0].isStrict, true);
191+
});
192+
});
193+
194+
it("can be without the semicolon in a function", () => {
195+
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
196+
const ast = espree.parse(`
197+
function foo() {
198+
"use strict"
199+
bar()
200+
}
201+
`, { ecmaVersion, range: true });
202+
203+
const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });
204+
205+
assert.strictEqual(globalScope.isStrict, false);
206+
assert.strictEqual(globalScope.childScopes.length, 1);
207+
assert.strictEqual(globalScope.childScopes[0].type, "function");
208+
assert.strictEqual(globalScope.childScopes[0].isStrict, true);
209+
});
210+
});
211+
212+
it("can be anywhere in the directive prologue in a function", () => {
213+
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
214+
const ast = espree.parse(`
215+
function foo() {
216+
"foo";
217+
"use strict";
218+
}
219+
`, { ecmaVersion, range: true });
220+
221+
const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });
222+
223+
assert.strictEqual(globalScope.isStrict, false);
224+
assert.strictEqual(globalScope.childScopes.length, 1);
225+
assert.strictEqual(globalScope.childScopes[0].type, "function");
226+
assert.strictEqual(globalScope.childScopes[0].isStrict, true);
227+
});
228+
});
229+
230+
it("cannot be after the directive prologue in a function", () => {
231+
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
232+
const ast = espree.parse(`
233+
function foo() {
234+
bar();
235+
"use strict";
236+
}
237+
`, { ecmaVersion, range: true });
238+
239+
const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });
240+
241+
assert.strictEqual(globalScope.isStrict, false);
242+
assert.strictEqual(globalScope.childScopes.length, 1);
243+
assert.strictEqual(globalScope.childScopes[0].type, "function");
244+
assert.strictEqual(globalScope.childScopes[0].isStrict, false);
245+
});
246+
});
247+
248+
it("cannot contain escapes in a function", () => {
249+
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
250+
const ast = espree.parse(`
251+
function foo() {
252+
"use \\strict";
253+
}
254+
`, { ecmaVersion, range: true });
255+
256+
const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });
257+
258+
assert.strictEqual(globalScope.isStrict, false);
259+
assert.strictEqual(globalScope.childScopes.length, 1);
260+
assert.strictEqual(globalScope.childScopes[0].type, "function");
261+
assert.strictEqual(globalScope.childScopes[0].isStrict, false);
262+
});
263+
});
264+
265+
it("cannot be parenthesized in a function", () => {
266+
getSupportedEcmaVersions({ min: 5 }).forEach(ecmaVersion => {
267+
const ast = espree.parse(`
268+
function foo() {
269+
("use strict");
270+
}
271+
`, { ecmaVersion, range: true });
272+
273+
const { globalScope } = analyze(ast, { ecmaVersion, childVisitorKeys: KEYS });
274+
275+
assert.strictEqual(globalScope.isStrict, false);
276+
assert.strictEqual(globalScope.childScopes.length, 1);
277+
assert.strictEqual(globalScope.childScopes[0].type, "function");
278+
assert.strictEqual(globalScope.childScopes[0].isStrict, false);
279+
});
280+
});
101281
});

0 commit comments

Comments
 (0)