You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
TypeScript parser rejects generic call signatures, export type * and variance annotations #97
The modifiers are only rejected in combination, and only on a property:
construct
parses
abstract readonly p: string
yes
override readonly p: string
yes
abstract override m(): void (method)
yes
abstract override p: string (property)
no
In uql this hits two files, dialect/abstractSqlDialect.ts:234 and dialect/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 readonly with abstract readonly in 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.
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.
interfaceG{<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:96 is the first of two generic call signatures in interface Get<E extends Env>, and the second is on line 97. src/utils/body.ts:94 is the closing line of the first of two generic call signatures in interface ParseBody. Reproduced on hono at HEAD, 8 of its src files 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-sitter and reading HasError() on the root node, before any polyscan code runs:
construct
typescript grammar
tsx grammar
two consecutive generic call signatures
error
error
variance annotation (TS 4.7)
error
error
export type * from (TS 5.0)
error
error
abstract override on a property (TS 4.3), from my earlier comment
error
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, the typescript and tsx grammars behave identically here, so the choice of tsx for .ts files at polyscan/internal/js/parser/parser.go:35 is 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, meaning import('./m').Foo<T>, import('./m').Foo[] or import('./m').Foo['k'].
The import type itself parses. It only fails once a suffix is applied to it:
construct
parses
import('./m').Foo
yes
import('./m').NS.Foo
yes
import('./m').Foo | string, & {a:1}
yes
typeof import('./m').foo
yes
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 type
yes
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 as cast the suffixed form parses, which is why a file can use the construct and still be analyzed:
In better-drizzle this accounts for 6 of the 7 skipped files (src/types/{delegate,hooks,query,runtime,transaction}.ts, src/plugins/zod/index.ts), and export type * from accounts 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
typescript
tsx
import('./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-sitter pin 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 MinParseErrorPenalty floor. 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.ts is skipped for export 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:
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.
A grammar bump does not fix this. smacker/go-tree-sitter is already pinned at its latest commit (dd81d9e, 2024-08-27), and the repository has had no commits since then.
I probed the official bindings (github.com/tree-sitter/go-tree-sitter v0.25.0) with tree-sitter-typescript v0.23.2, the latest release, and with its master branch (75b3874, 2025-01-30). All five constructs in this issue still produce HasError() in both the typescript and tsx grammars:
construct
v0.23.2
master
two consecutive generic call signatures
error
error
export type * from
error
error
variance annotations (in/out)
error
error
abstract override on a property
error
error
import type with a <T>, [] or ['k'] suffix
error
error
controls: satisfies, accessor, export type { A }, a generic interface
ok
ok
A fix therefore needs grammar changes: a patched fork of tree-sitter-typescript with a regenerated parser.c. Switching to the official Go bindings alone does not help.
Activity
DaisukeYoda commentedon Sep 17, 2026
Another construct in this same class, found by the FP-audit skill on rogerpadilla/uql@
6252e1f:abstract overrideon a property declaration (TS 4.3+).The modifiers are only rejected in combination, and only on a property:
abstract readonly p: stringoverride readonly p: stringabstract override m(): void(method)abstract override p: string(property)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: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).DaisukeYoda commentedon Sep 17, 2026
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.
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:typescriptgrammartsxgrammarexport type * from(TS 5.0)abstract overrideon a property (TS 4.3), from my earlier commentTwo 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.DaisukeYoda commentedon Sep 21, 2026
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:
import('./m').Fooimport('./m').NS.Fooimport('./m').Foo | string,& {a:1}typeof import('./m').fooimport('./m').Foo<string>import('./m').Foo[]import('./m').Foo['k']import('./m').NS.Foo<string>Foo<string>/Foo[]on an ordinary imported typeThe 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: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:typescripttsximport('./m').Foo<string>import('./m').Foo[]import('./m').Foo['k']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:
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: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).DaisukeYoda commentedon Oct 15, 2026
A grammar bump does not fix this.
smacker/go-tree-sitteris already pinned at its latest commit (dd81d9e, 2024-08-27), and the repository has had no commits since then.I probed the official bindings (
github.com/tree-sitter/go-tree-sitterv0.25.0) with tree-sitter-typescript v0.23.2, the latest release, and with its master branch (75b3874, 2025-01-30). All five constructs in this issue still produceHasError()in both thetypescriptandtsxgrammars:export type * fromin/out)abstract overrideon a property<T>,[]or['k']suffixsatisfies,accessor,export type { A }, a generic interfaceA fix therefore needs grammar changes: a patched fork of tree-sitter-typescript with a regenerated
parser.c. Switching to the official Go bindings alone does not help.