Repository navigation
TypeScript parser rejects generic call signatures, export type * and variance annotations #97
Description
Activity
Another construct in this same class, found by the FP-audit skill on rogerpadilla/uql@
6252e1f:abstract overrideon a property declaration (TS 4.3+).abstract class Base { abstract p: string; } export abstract class C extends Base { abstract override p: string; }
[e.ts] parse_error: syntax error at line 2The modifiers are only rejected in combination, and only on a property:
construct parses abstract readonly p: stringyes override readonly p: stringyes abstract override m(): void(method)yes abstract override p: string(property)no In
uqlthis hits two files,dialect/abstractSqlDialect.ts:234anddialect/vectorSqlDialect.ts:26.Score impact
Worth recording here because this issue notes that skipped files are removed from every score but does not quantify it. Replacing
abstract override readonlywithabstract readonlyin those two files, changing nothing else:skipped analyzed functions LOC health grade as-is 2 280 3635 44584 57 D one-token patch 0 282 3831 47282 69 C Two unparsable files out of 282 cost 12 health points and a full letter grade, and hide 196 functions and 2698 LOC. The parse-error penalty and the excluded content compound, so the blast radius of each construct listed in this issue is larger than the file count suggests.
polyscan
0.4.0(be31afc).Follow-up on the first bullet, which I think is mis-stated, plus a grammar-level probe that tells you what one fix would cover.
The generic call signature case needs two of them, not one
A single generic call signature parses fine. The trigger is two or more consecutive generic call signatures in the same interface or type literal, which is to say a generic overload set.
interface G { <T>(x: T): T // alone: parses <U>(x: U): U // second one: syntax error on this line }
shape parses one generic call signature yes generic call signature followed by a non-generic one yes two consecutive generic call signatures no three consecutive no That matches the files listed in the issue.
src/context.ts:96is the first of two generic call signatures ininterface Get<E extends Env>, and the second is on line 97.src/utils/body.ts:94is the closing line of the first of two generic call signatures ininterface ParseBody. Reproduced on hono at HEAD, 8 of itssrcfiles skipped, same line numbers as the issue reports.The distinction matters because "generic call signatures are rejected" sends a reader looking at the wrong construct. The type parameters are not the problem. Two of them in a row are.
All of these fail inside tree-sitter, in both grammars
I probed the vendored grammar directly, parsing each construct with
smacker/go-tree-sitterand readingHasError()on the root node, before any polyscan code runs:construct typescriptgrammartsxgrammartwo consecutive generic call signatures error error variance annotation (TS 4.7) error error export type * from(TS 5.0)error error abstract overrideon a property (TS 4.3), from my earlier commenterror error plain class, control ok ok Two things follow. First, none of these are polyscan-side handling, so a single grammar bump should clear all four constructs at once. The pin is
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82, from August 2024. Second, thetypescriptandtsxgrammars behave identically here, so the choice oftsxfor.tsfiles atpolyscan/internal/js/parser/parser.go:35is not a contributing factor. I checked that specifically because it looked like a plausible cause and it is not one.Another construct in this class, found by the FP-audit skill on almeidazs/better-drizzle@
23527ae: an import type carrying a type-operator suffix, meaningimport('./m').Foo<T>,import('./m').Foo[]orimport('./m').Foo['k'].The import type itself parses. It only fails once a suffix is applied to it:
construct parses import('./m').Fooyes import('./m').NS.Fooyes import('./m').Foo | string,& {a:1}yes typeof import('./m').fooyes import('./m').Foo<string>no import('./m').Foo[]no import('./m').Foo['k']no import('./m').NS.Foo<string>no Foo<string>/Foo[]on an ordinary imported typeyes The same suffixes on an ordinary type reference are fine, so the suffix grammar works in general and only the import-type operand is missing it.
One position is exempt. In an
ascast the suffixed form parses, which is why a file can use the construct and still be analyzed:const y = x as unknown as import('./m').Foo<string>; // parses type A = import('./m').Foo<string>; // syntax error
In
better-drizzlethis accounts for 6 of the 7 skipped files (src/types/{delegate,hooks,query,runtime,transaction}.ts,src/plugins/zod/index.ts), andexport type * fromaccounts for the seventh.Confirmed in the grammar, both variants
Same probe method as my earlier comment, reading
HasError()on the root node before any polyscan code runs:construct typescripttsximport('./m').Foo<string>error error import('./m').Foo[]error error import('./m').Foo['k']error error plain class, control ok ok So this is the same situation as the four constructs already listed. It sits in the vendored grammar rather than in polyscan, and a single bump of the
smacker/go-tree-sitterpin should clear it along with the rest.Score impact
Patching only the three failing constructs in those 7 files, changing nothing else:
skipped analyzed functions LOC health grade as-is 7 87 789 23311 73 C patched 0 94 801 28006 82 B Nine health points and a full letter grade. The 4695 hidden LOC are 17 percent of the real total, so a reader of the as-is report is judging about five sixths of the tree.
The penalty arithmetic reconciles exactly in both runs: raw dimension penalties sum to 15.40 against a 96 point budget, giving 84 before the parse penalty, and the reported 73 implies a parse penalty of exactly 11, which is the
MinParseErrorPenaltyfloor. The patched run lands on 82 with a parse penalty of 0.Skipped files distort other analyses, in both directions
This is the part I had not expected, and it argues the blast radius is wider than the health score.
A skipped file's import edges vanish, so symbols it consumes look dead.
src/plugins/rules/index.tsis skipped forexport type *, and it re-exports four functions from./shared/presets. All four are reported as dead code, together with a fifth export the same barrel consumes:unused_exported_function src/plugins/rules/shared/presets.ts:3 'mergeRules' unused_exported_function src/plugins/rules/shared/presets.ts:14 'safe' unused_exported_function src/plugins/rules/shared/presets.ts:60 'recommended' unused_exported_function src/plugins/rules/shared/presets.ts:152 'strict' unused_export src/plugins/rules/shared/plugin.ts:1242 'default'All five disappear once the barrel parses. They are false positives manufactured by the parse failure, in files that parsed perfectly well themselves, which means a parse error is not contained to the file that caused it.
In the other direction, the dependency score moves from 85 to 75 once the files parse. The excluded files were hiding real dependency debt, so that dimension read healthier than the truth while the flat 11 point penalty made the total read worse. Parse errors are not a uniform downward bias on the report, and a reader cannot correct for them by assuming the real grade is somewhat better.
polyscan
0.4.1(e767a21).
Recent TypeScript syntax makes the parser skip whole files, which removes them from every score.
Failing constructs, with the file and line the error points at:
<Key extends keyof E['Variables']>(key: Key): E['Variables'][Key]: honosrc/context.ts:96,src/jsx/hooks/index.ts:179,src/helper/ssg/middleware.ts:32,src/utils/body.ts:94,src/types.ts,src/helper/factory/index.tsexport type * from './types'(TS 5.0): honosrc/jsx/index.ts:112,src/jsx/dom/index.ts:169interface ZodType<out Output = unknown, out Input = unknown>(TS 4.7): zodpackages/zod/src/v4/classic/schemas.ts:34,v4/core/checks.ts:28,v4/core/schemas.ts:182,v4/mini/schemas.ts:9Reproduce with
polyscan analyze srcon hono (8 of 311 files skipped) andpolyscan analyze packages/zod/srcon zod (4 of 327 skipped).