@@ -60,18 +60,22 @@ class StrictCompatibilityPlugin {
6060
6161parser . hooks . program . tap ( PLUGIN_NAME , ( ast ) => {
6262const mod = parser . state . module ;
63- const sourceObj = mod . originalSource && mod . originalSource ( ) ;
64- const src =
65- sourceObj && typeof sourceObj . source === "function"
66- ? sourceObj . source ( )
67- : undefined ;
63+
64+ /**
65+ * @param { string } msg message
66+ * @param { DependencyLocation | undefined } loc location
67+ */
6868const reportError = ( msg , loc ) => {
6969if ( mode === "error" ) {
7070mod . addError ( new StrictCompatibilityError ( msg , loc ) ) ;
7171} else {
7272mod . addWarning ( new StrictCompatibilityWarning ( msg , loc ) ) ;
7373}
7474} ;
75+ /**
76+ *@param {string } msg message
77+ *@param {DependencyLocation | undefined } loc location
78+ */
7579const reportWarning = ( msg , loc ) => {
7680mod . addWarning ( new StrictCompatibilityWarning ( msg , loc ) ) ;
7781} ;
@@ -90,6 +94,10 @@ class StrictCompatibilityPlugin {
9094functionDepth -- ;
9195} ;
9296
97+ /**
98+ *@param {import("estree").Pattern | null | undefined } pattern pattern to collect from
99+ *@param {string[] } into array to collect identifiers into
100+ */
93101const collectIdentsFromPattern = ( pattern , into ) => {
94102if ( ! pattern ) return ;
95103switch ( pattern . type ) {
@@ -149,21 +157,21 @@ class StrictCompatibilityPlugin {
149157if ( seen . has ( n ) ) {
150158reportError (
151159`Duplicate parameter name '${ n } ' is not allowed in strict mode` ,
152- node . loc
160+ node . loc || undefined
153161) ;
154162} else {
155163seen . add ( n ) ;
156164}
157165if ( n === "eval" || n === "arguments" ) {
158166reportError (
159167`Using '${ n } ' as parameter name is not allowed in strict mode` ,
160- node . loc
168+ node . loc || undefined
161169) ;
162170}
163171if ( STRICT_RESERVED_WORDS . has ( n ) ) {
164172reportError (
165173`Using reserved word '${ n } ' as parameter name is not allowed in strict mode` ,
166- node . loc
174+ node . loc || undefined
167175) ;
168176}
169177}
@@ -177,6 +185,9 @@ class StrictCompatibilityPlugin {
177185 *@param {import("./Dependency").DependencyLocation | undefined } loc Source location for diagnostics
178186 */
179187const checkDeclId = ( id , loc ) => {
188+ /**
189+ *@param {string } n name to check
190+ */
180191const checkName = ( n ) => {
181192if ( n === "eval" || n === "arguments" ) {
182193reportError (
@@ -207,6 +218,20 @@ class StrictCompatibilityPlugin {
207218// unresolvable references via parser.isVariableDefined(name)
208219// (Annex C / 6.2.5.6, 13.15)
209220
221+ /**
222+ * Type guard for ESTree Node.
223+ *@param {unknown } x value to test
224+ *@returns {x is import("estree").Node } true if the value looks like an ESTree Node
225+ */
226+ const isNode = ( x ) => {
227+ if ( typeof x !== "object" || x === null ) return false ;
228+ const r = /**@type {Record<string, unknown> } */ ( x ) ;
229+ return typeof r . type === "string" ;
230+ } ;
231+
232+ /**
233+ *@param {import("estree").Node | null | undefined } node node to walk
234+ */
210235const walk = ( node ) => {
211236if ( ! node || typeof node . type !== "string" ) return ;
212237switch ( node . type ) {
@@ -217,7 +242,7 @@ class StrictCompatibilityPlugin {
217242// ECMA-262 Strict Mode: with statement is prohibited (Annex C / 14.11.1)
218243reportError (
219244"with statement is not allowed in strict mode" ,
220- node . loc
245+ node . loc || undefined
221246) ;
222247walk ( node . object ) ;
223248walk ( node . body ) ;
@@ -231,7 +256,7 @@ class StrictCompatibilityPlugin {
231256) {
232257reportError (
233258"Deleting an unqualified identifier is not allowed in strict mode (delete x)" ,
234- node . loc
259+ node . loc || undefined
235260) ;
236261}
237262walk ( node . argument ) ;
@@ -246,7 +271,7 @@ class StrictCompatibilityPlugin {
246271) {
247272reportError (
248273`Using '${ node . argument . name } ' as operand of update expression is not allowed in strict mode` ,
249- node . loc
274+ node . loc || undefined
250275) ;
251276}
252277return ;
@@ -257,21 +282,21 @@ class StrictCompatibilityPlugin {
257282if ( n === "eval" || n === "arguments" ) {
258283reportError (
259284`Assignment to '${ n } ' is not allowed in strict mode` ,
260- node . loc
285+ node . loc || undefined
261286) ;
262287}
263288// ECMA-262 Strict Mode: Unresolved references cannot be assigned (Annex C / 6.2.5.6, 13.15)
264289if ( ! parser . isVariableDefined ( n ) ) {
265290reportError (
266291`Assignment to undeclared identifier '${ n } ' is not allowed in strict mode` ,
267- node . loc
292+ node . loc || undefined
268293) ;
269294}
270295// ECMA-262 Strict Mode: Assignment to read-only global values (Annex C / 13.15)
271296if ( n === "undefined" || n === "Infinity" || n === "NaN" ) {
272297reportError (
273298`Assignment to read-only global '${ n } ' is not allowed in strict mode` ,
274- node . loc
299+ node . loc || undefined
275300) ;
276301}
277302} else if (
@@ -282,8 +307,14 @@ class StrictCompatibilityPlugin {
282307// Flag assignment to those properties as well.
283308const obj = node . left . object ;
284309const prop = node . left . computed
285- ?node . left . property && node . left . property . value
286- :node . left . property && node . left . property . name ;
310+ ?node . left . property &&
311+ /**@type {import("estree").Literal } */ (
312+ node . left . property
313+ ) . value
314+ :node . left . property &&
315+ /**@type {import("estree").Identifier } */ (
316+ node . left . property
317+ ) . name ;
287318if (
288319obj &&
289320obj . type === "Identifier" &&
@@ -292,7 +323,7 @@ class StrictCompatibilityPlugin {
292323) {
293324reportWarning (
294325`Assignment to 'arguments.${ prop } ' may throw in strict mode` ,
295- node . loc
326+ node . loc || undefined
296327) ;
297328}
298329}
@@ -302,7 +333,10 @@ class StrictCompatibilityPlugin {
302333case "ArrowFunctionExpression" :
303334checkParams ( node ) ;
304335if ( node . type !== "ArrowFunctionExpression" ) {
305- checkDeclId ( node . id , node . loc ) ;
336+ checkDeclId (
337+ node . id ,
338+ /**@type {DependencyLocation | undefined } */ ( node . loc )
339+ ) ;
306340}
307341enterFn ( ) ;
308342if ( node . body ) {
@@ -316,22 +350,29 @@ class StrictCompatibilityPlugin {
316350return ;
317351case "ClassDeclaration" :
318352// ECMA-262 Strict Mode: BindingIdentifier must not be eval/arguments even for class names (Annex C / 13.1.1)
319- checkDeclId ( node . id , node . loc ) ;
353+ checkDeclId (
354+ node . id ,
355+ /**@type {DependencyLocation | undefined } */ ( node . loc )
356+ ) ;
320357if ( node . body ) {
321358for ( const el of node . body . body ) walk ( el ) ;
322359}
323360return ;
324361case "VariableDeclaration" :
325362for ( const d of node . declarations ) {
326- checkDeclId ( d . id , d . loc ) ;
363+ checkDeclId ( d . id , d . loc || undefined ) ;
327364if ( d . init ) walk ( d . init ) ;
328365}
329366return ;
330367case "MemberExpression" :{
331368const obj = node . object ;
332369const prop = node . computed
333- ?node . property && node . property . value
334- :node . property && node . property . name ;
370+ ?node . property &&
371+ /**@type {import("estree").Literal } */ ( node . property )
372+ . value
373+ :node . property &&
374+ /**@type {import("estree").Identifier } */ ( node . property )
375+ . name ;
335376if (
336377obj &&
337378obj . type === "Identifier" &&
@@ -340,7 +381,7 @@ class StrictCompatibilityPlugin {
340381) {
341382reportWarning (
342383`'arguments.${ prop } ' is forbidden in strict mode` ,
343- node . loc
384+ node . loc || undefined
344385) ;
345386}
346387// NOTE: Accessing Function#caller/arguments on strict functions throws a TypeError at runtime.
@@ -352,7 +393,7 @@ class StrictCompatibilityPlugin {
352393) {
353394reportWarning (
354395`Accessing '.${ prop } ' may be restricted in strict mode` ,
355- node . loc
396+ node . loc || undefined
356397) ;
357398}
358399walk ( node . object ) ;
@@ -364,28 +405,34 @@ class StrictCompatibilityPlugin {
364405if ( functionDepth === 0 ) {
365406reportWarning (
366407"Top-level 'this' may be undefined in strict mode" ,
367- node . loc
408+ node . loc || undefined
368409) ;
369410}
370411return ;
371412case "CatchClause" :
372413// ECMA-262 Strict Mode: catch parameter must not be eval/arguments (Annex C / 14.15.1)
373414if ( node . param ) {
374- checkDeclId ( node . param , node . loc ) ;
415+ checkDeclId (
416+ node . param ,
417+ /**@type {DependencyLocation | undefined } */ ( node . loc )
418+ ) ;
375419}
376420if ( node . body ) {
377421for ( const st of node . body . body ) walk ( st ) ;
378422}
379423return ;
380424default :{
381- for ( const k in node ) {
382- const v = node [ k ] ;
425+ const rec = /**@type {Record<string, unknown> } */ (
426+ /**@type {unknown } */ ( node )
427+ ) ;
428+ for ( const k in rec ) {
429+ const v = rec [ k ] ;
383430if ( ! v ) continue ;
384431if ( Array . isArray ( v ) ) {
385432for ( const c of v ) {
386- if ( c && typeof c . type === "string" ) walk ( c ) ;
433+ if ( isNode ( c ) ) walk ( c ) ;
387434}
388- } else if ( v && typeof v . type === "string" ) {
435+ } else if ( isNode ( v ) ) {
389436walk ( v ) ;
390437}
391438}
@@ -404,42 +451,9 @@ class StrictCompatibilityPlugin {
404451// These errors are raised at runtime by the engine in strict mode.
405452// ---------------------------------------------------------------------------
406453
407- // Numeric literals / string escapes: detect directly from source text
408- // (most cases are parser-rejected already)
409- if ( src && typeof src === "string" ) {
410- // ECMA-262 Strict Mode: Legacy octal numeric literals are prohibited
411- // - (Annex C / 7-10)
412- // Legacy octal integer starting with 0 but not 0x/0o/0b (e.g. 0644)
413- const octalNum = / ( ^ | [ ^ . \w ] ) 0 [ 0 - 7 ] + (? ! [ 0 - 9 ] ) / m;
414- // ECMA-262 Strict Mode: NonOctalDecimalIntegerLiteral (e.g. 08, 09) is prohibited
415- // - (Annex C / 7-10)
416- const nonOctalDecimalNum = / ( ^ | [ ^ . \w ] ) 0 [ 0 - 9 ] + (? ! [ 0 - 9 ] ) / m;
417- // ECMA-262 Strict Mode: Legacy octal escape & NonOctalDecimalEscape (\8, \9) are prohibited
418- // - (Annex C / 11-13)
419- // Legacy octal escapes like \\1, \\12, \\123 (\\0 alone is allowed)
420- const octalEsc = / \\ (?: [ 1 - 7 ] [ 0 - 7 ] { 0 , 2 } ) / m;
421- // Non-octal decimal escape sequences: \8 or \9
422- const nonOctalDecimalEsc = / \\ [ 8 9 ] / m;
423- if ( octalNum . test ( src ) ) {
424- reportError (
425- "Legacy octal numeric literal is not allowed in strict mode (use 0o...)" ,
426- /**@type {DependencyLocation } */ ( mod . loc )
427- ) ;
428- }
429- // Non-octal decimal literal with a leading zero (e.g. 08, 09)
430- if ( ! octalNum . test ( src ) && nonOctalDecimalNum . test ( src ) ) {
431- reportError (
432- "Non-octal decimal integer literal with leading zero is not allowed in strict mode (e.g. 08, 09)" ,
433- /**@type {DependencyLocation } */ ( mod . loc )
434- ) ;
435- }
436- if ( octalEsc . test ( src ) || nonOctalDecimalEsc . test ( src ) ) {
437- reportError (
438- "Legacy/non-octal decimal escape sequences in string literals are not allowed in strict mode" ,
439- /**@type {DependencyLocation } */ ( mod . loc )
440- ) ;
441- }
442- }
454+ // NOTE (Annex C 7–13): Legacy octal numeric literals and legacy/non-octal decimal
455+ // escape sequences are rejected by the parser in strict mode. To keep pack caches
456+ // stable we do not scan raw source; such cases surface as Acorn "Module parse failed".
443457} ) ;
444458} ;
445459