From 002f9df43adc900e9911b3b23cd1b26cc2248c6f Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:23:34 +0800 Subject: [PATCH] fix(javascript): derive program strictness from parser context --- .../javascript/javascriptSemanticAnalysis.ts | 11 ++-------- .../javascriptSemanticOpaqueEffects.ts | 11 ++++++---- .../javascript/javascriptSourceParser.test.ts | 20 +++++++++++++++++++ .../javascript/javascriptSourceParser.ts | 14 +++++++++++++ 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/domain/javascript/javascriptSemanticAnalysis.ts b/src/domain/javascript/javascriptSemanticAnalysis.ts index 94647dc741..ebbff87aa0 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.ts @@ -100,6 +100,7 @@ import { import { semanticResourceLimitReason } from "./javascriptSemanticResourceLimits.js"; import { javaScriptRuntimeContext, + javaScriptProgramStrictness, parseJavaScriptSource, type ParsedJavaScriptSource, } from "./javascriptSourceParser.js"; @@ -1799,15 +1800,7 @@ const semanticStrictnessAt = ( current.body.directives.some(({ value }) => value.value === "use strict") ) return "strict"; - if (t.isProgram(current)) { - if (current.sourceType === "module") return "strict"; - if (current.directives.some(({ value }) => value.value === "use strict")) - return "strict"; - const runtimeContext = javaScriptRuntimeContext(current); - if (runtimeContext === "module") return "strict"; - if (runtimeContext === "commonjs") return "sloppy"; - return "unknown"; - } + if (t.isProgram(current)) return javaScriptProgramStrictness(current); } return "unknown"; }; diff --git a/src/domain/javascript/javascriptSemanticOpaqueEffects.ts b/src/domain/javascript/javascriptSemanticOpaqueEffects.ts index 3fd065f070..f602bc272a 100644 --- a/src/domain/javascript/javascriptSemanticOpaqueEffects.ts +++ b/src/domain/javascript/javascriptSemanticOpaqueEffects.ts @@ -1,4 +1,5 @@ import * as t from "@babel/types"; +import { javaScriptProgramStrictness } from "./javascriptSourceParser.js"; import { unwrapJavaScriptExpression } from "./javascriptAstValues.js"; import { createAmbientPathResolver } from "./javascriptSemanticAmbientWrites.js"; import { createSemanticModuleInterop } from "./javascriptModuleInterop.js"; @@ -238,11 +239,13 @@ const strictEval = ( node !== undefined; node = state.parentsByNode.get(node) ) { - if (t.isClass(node) || (t.isProgram(node) && node.sourceType === "module")) + if ( + t.isClass(node) || + (t.isProgram(node) && javaScriptProgramStrictness(node) === "strict") + ) return true; - const directives = t.isProgram(node) - ? node.directives - : t.isFunction(node) && t.isBlockStatement(node.body) + const directives = + t.isFunction(node) && t.isBlockStatement(node.body) ? node.body.directives : []; if (directives.some(({ value }) => value.value === "use strict")) diff --git a/src/domain/javascript/javascriptSourceParser.test.ts b/src/domain/javascript/javascriptSourceParser.test.ts index ae1a9ed6fe..5f1f62024b 100644 --- a/src/domain/javascript/javascriptSourceParser.test.ts +++ b/src/domain/javascript/javascriptSourceParser.test.ts @@ -6,6 +6,7 @@ import { parseRecoveredJavaScript, parserPluginsForPath, javaScriptRuntimeContext, + javaScriptProgramStrictness, } from "./javascriptSourceParser.js"; import { analyzeParsedJavaScriptStaticSource } from "./javascriptStaticAnalysis.js"; @@ -48,6 +49,25 @@ it.each([ }, ); +it.each([ + ["const args=[]; export = args;", "fixture.ts", "sloppy"], + ['"use strict"; const args=[]; export = args;', "fixture.ts", "strict"], + ['import type {T} from "example";', "fixture.ts", "unknown"], + ["const args=[];", "fixture.mjs", "strict"], + ["const args=[];", "fixture.cjs", "sloppy"], +] as const)( + "owns program strictness independently of parser syntax: %s (%s)", + (source, path, strictness) => { + const parsed = parseJavaScriptSource(source, path); + if (parsed === null) throw new Error("Expected parsed source"); + expect(javaScriptProgramStrictness(parsed.program)).toBe(strictness); + const recovered = parseRecoveredJavaScript(source, path, { + plugins: parserPluginsForPath(path), + }).file; + expect(javaScriptProgramStrictness(recovered.program)).toBe(strictness); + }, +); + const analyze = (source: string, path: string) => { const parsed = parseJavaScriptSource(source, path); return parsed === null diff --git a/src/domain/javascript/javascriptSourceParser.ts b/src/domain/javascript/javascriptSourceParser.ts index 70c66b9a82..82bff08cb8 100644 --- a/src/domain/javascript/javascriptSourceParser.ts +++ b/src/domain/javascript/javascriptSourceParser.ts @@ -33,6 +33,20 @@ export const javaScriptRuntimeContext = ( ): JavaScriptRuntimeContext => runtimeContextByProgram.get(program) ?? "unknown"; +/** Runtime strictness follows parser-owned mode, not erased TypeScript syntax. */ +export const javaScriptProgramStrictness = ( + program: t.Program, +): "strict" | "sloppy" | "unknown" => { + if (program.directives.some(({ value }) => value.value === "use strict")) + return "strict"; + const context = javaScriptRuntimeContext(program); + return context === "module" + ? "strict" + : context === "commonjs" + ? "sloppy" + : "unknown"; +}; + const hasRuntimeModuleDeclaration = (program: t.Program): boolean => program.body.some((node) => { if (t.isTSImportEqualsDeclaration(node))