fix(javascript): parse JavaScript sources with JavaScript grammar (#3782)
parserPluginsForPath enabled Babel's typescript plugin for .js, .jsx, .mjs and .cjs sources. TypeScript reads some valid JavaScript differently: a conditional whose consequent is an arrow with a parenthesized body (c?x=>(a):y=>(b)) throws as a return-type annotation, and a<b>(c) becomes a generic call. VS Code's 20 MB workbench bundles therefore returned no AST at all. Select JavaScript grammar (JSX plus legacy decorators) for explicit JavaScript extensions. TypeScript dialects and unnamed sources keep the existing TypeScript-capable contract. Fixes #3780 Refs #3670 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
59f528f0e5
commit
44b197f924
3 files changed
+53
-4
No files matched your search
@@ -206,6 +206,9 @@ code unit, matching the original bytes identified by the artifact digest.
|
||||
|
||||
Plain `.ts` artifact sources use TypeScript syntax without JSX, including
|
||||
angle-bracket type assertions. `.tsx` and `.jsx` retain JSX parsing.
|
||||
JavaScript sources use JavaScript grammar with JSX, never TypeScript syntax,
|
||||
so valid JavaScript that TypeScript reads differently keeps its JavaScript
|
||||
meaning.
|
||||
|
||||
Relative TypeScript imports can name their emitted `.js`, `.mjs`, or `.cjs`
|
||||
counterparts while resolving to inventoried source files. This substitution
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { parseJavaScriptSource } from "./javascriptSourceParser.js";
|
||||
import { analyzeParsedJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
|
||||
|
||||
const analyze = (source: string, path: string) => {
|
||||
const parsed = parseJavaScriptSource(source, path);
|
||||
return parsed === null
|
||||
? null
|
||||
: {
|
||||
errors: parsed.errors.length,
|
||||
facts: analyzeParsedJavaScriptStaticSource(source, parsed),
|
||||
};
|
||||
};
|
||||
|
||||
it("parses conditional arrow bodies in JavaScript sources with JavaScript grammar", () => {
|
||||
// Bundlers emit this shape; TypeScript reads `):H=>` as a return type.
|
||||
const source = `const {ipcMain}=require("electron");
|
||||
const pick=n===0?H=>({start:H.startY}):H=>({start:H.startX});
|
||||
ipcMain.handle("layout:ready",()=>pick);`;
|
||||
for (const path of ["main.js", "main.cjs", "main.mjs", "main.jsx"]) {
|
||||
const result = analyze(source, path);
|
||||
expect(result?.errors).toBe(0);
|
||||
expect(result?.facts.electron.ipc).toContainEqual(
|
||||
expect.objectContaining({ channel: "layout:ready", operation: "handle" }),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps JavaScript comparison meaning for angle brackets in JavaScript sources", () => {
|
||||
const source = `const a=fetch<limit>("/api/compare");`;
|
||||
expect(analyze(source, "main.js")?.facts.endpoints).toEqual([]);
|
||||
expect(analyze(source, "main.ts")?.facts.endpoints).toContainEqual(
|
||||
expect.objectContaining({ value: "/api/compare" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps TypeScript syntax available for TypeScript and unnamed sources", () => {
|
||||
const source = "let count: number = 1; export type Id = string;";
|
||||
expect(parseJavaScriptSource(source, "value.ts")?.errors).toEqual([]);
|
||||
expect(parseJavaScriptSource(source)?.errors).toEqual([]);
|
||||
});
|
||||
@@ -9,17 +9,21 @@ export type ParsedJavaScriptSource = ReturnType<typeof parse>;
|
||||
*
|
||||
* - `dts:true` only for `.d.ts/.d.mts/.d.cts` ambient declarations;
|
||||
* ordinary `.ts` keeps the missing-initializer diagnostic.
|
||||
* - JSX enabled for `.tsx`, `.jsx`, `.js`, and unknown paths; disabled for
|
||||
* plain `.ts/.cts/.mts` (angle-bracket assertions, not JSX) and for all
|
||||
* - `.js/.jsx/.mjs/.cjs` use JavaScript grammar with JSX. TypeScript reads
|
||||
* some valid JavaScript differently (`c?x=>(a):y=>(b)` as a return type,
|
||||
* `a<b>(c)` as a generic call), so it must not select their meaning.
|
||||
* - JSX enabled for `.tsx` and unknown paths; disabled for plain
|
||||
* `.ts/.cts/.mts` (angle-bracket assertions, not JSX) and for all
|
||||
* declaration files. `.mts` (including `.d.mts`) sets
|
||||
* `disallowAmbiguousJSXLike`, matching tsc's `.mts` mode.
|
||||
* - `decorators-legacy` always present: without it decorated sources fail
|
||||
* hard instead of reporting recovered syntax, losing every derived fact.
|
||||
* - Unknown paths retain the existing JSX-capable contract (best-effort
|
||||
* parse; failures surface via `errorRecovery`, never silent misparse).
|
||||
* - Unknown paths retain the existing JSX- and TypeScript-capable contract
|
||||
* (best-effort parse; failures surface via `errorRecovery`).
|
||||
*/
|
||||
export const parserPluginsForPath = (sourcePath?: string): ParserPlugin[] => {
|
||||
const path = sourcePath ?? "";
|
||||
if (/\.(?:js|jsx|mjs|cjs)$/iu.test(path)) return ["decorators-legacy", "jsx"];
|
||||
const dts = /\.d\.(?:ts|mts|cts)$/iu.test(path);
|
||||
const isTsLike = /\.(?:ts|cts|mts)$/iu.test(path);
|
||||
const isTsx = /\.tsx$/iu.test(path);
|
||||
|
||||
Reference in new issue
Block a user