fix(conformance): close ~170 hybrid gaps (parserServices gating, console global, type-param constraints)#7
Open
ericsssan wants to merge 7 commits into
Open
fix(conformance): close ~170 hybrid gaps (parserServices gating, console global, type-param constraints)#7ericsssan wants to merge 7 commits into
ericsssan wants to merge 7 commits into
Conversation
JS parses skip stashLastParse but still update tl_tagged_gen. With the old conditional-invalidate, a prior TS parse's stash stayed live: the next openReuseImpl for a JS gen found a tag match but loaded the wrong (TS) AST, passing stale parent_indices to Checker.init() → infinite loop in enclosingScopeIdx (no bounds check in ReleaseFast). Fix: unconditionally call invalidateReuseStash() at the start of every parse so JS parses leave tl_last_ast=null. openReuseImpl then falls back to the fresh-parse path as intended. Also removes the _activeRuleId allowlist gate from the `program` getter so any rule that accesses parserServices.program gets the real facade, and updates the baseline to match the resulting coverage changes.
…sonarjs FP cluster
Differential hybrid gaps reduced substantially with zero regressions, validated
via the full corpus run each step.
parserServices type-info gating (js/eslint-runner.js) — the single biggest win:
the light parserServices exposed a non-null `program` and TS node maps
unconditionally, so type-aware plugin rules (sonarjs/*, eslint-plugin-n) ran and
fired where the oracle (no projectService/project) bails via
isRequiredParserServices. Now gated on `_typeInfoRequested`:
- `program` is null when no type info was requested → rules bail like the oracle.
- TS node maps suppressed only for plain-JS files w/o type info (espree parity);
TS files keep them (matching @typescript-eslint/parser: maps + null program),
so naming-convention etc. are unaffected.
- undefined flag (LSP / non-differential entry points) preserves prior behavior.
Cleared the sonarjs FP cluster (unused-named-groups, new-operator-misuse,
post-message, no-alphabetical-sort, non-number-in-arithmetic-expression,
in-operator-type-error). Facade rules verified unchanged.
no-confusing-void-expression 52/107 -> 107/107: `console` was wrongly in
BUILTIN_ES2015_GLOBALS (it's a host/env global, not an ECMAScript builtin). The
pre-declared implicit-global symbol shadowed ez-checker's curated `Console`
type, so console.log(...) typed as `any` not `void`. Removed it; unresolved
`console` still reports as a global ref via isGlobalReference.
strict-boolean-expressions 185/212 -> 208/212: walkType now follows a generic
type param's constraint (`T extends boolean` -> boolean) instead of treating it
as `any`, matching getConstrainedTypeAtLocation.
no-unnecessary-condition: 16 noOverlapBooleanExpression FNs closed —
involvesIndeterminateType / typesCanOverlap now resolve type params to their
constraint (`<T extends object>(a: T)` vs null/undefined fires correctly).
consistent-type-imports 16 -> 4 gaps: detect `export type { X }` (es-parser emits
`read` not `type_read` for those specifiers, #33) via symbolRefsAllInTypeExports.
Suppress the JSX-factory FP (es-parser omits the implicit factory ref, #34) only
for the precise candidate: a default import whose name matches the JSX pragma
(default React; honours settings.react.pragma) in a file that actually contains
JSX elements — so a genuinely type-only default import, or any import in a
JSX-free file, is still reported.
no-unnecessary-type-conversion: type_param constraint handling in typeIsBigIntish.
…erences `type T = A | A` where `A` is undeclared was flagged as a duplicate (3 FPs). TypeScript types an undeclared name as the error type, and two error types are non-equivalent, so TSe does not report. The checker can't distinguish an undeclared name from a type parameter (both resolve as "unresolved") and surfaces undeclared names as a named type_ref/any rather than the error type, so `typeNodeIsError` now also treats a type_reference whose name is neither a known type NOR an in-scope type parameter as the error type. Type parameters (`f<T>(): T | T`) and lib/user types still flag correctly. no-duplicate-type-constituents 79/82 -> 82/82. Hybrid 153 -> 150 gaps, 0 regressions.
…s variables
`const p = "[👍]"; new RegExp(p)` and `const flags = ""; new RegExp("[\u{1f44d}]", flags)`
were skipped because the pattern/flags arguments weren't string/template literals.
Now resolve an effective-const identifier to its string-literal initializer via
constInitializerOf, for both the pattern and the flags argument.
When the pattern comes from a resolved constant, char offsets can't be mapped
back into the original source, so ESLint reports on the whole argument node —
CallSourceMap gains an `override` span that collapses every reported span to the
identifier. allowEscape is also disabled on that path (escape forms in the
variable's literal aren't "visible" at the usage site), matching ESLint.
no-misleading-character-class 165/174 -> 168/174. Hybrid 150 -> 147 gaps, 0 regressions.
…(surrogate-aware) ESLint's `zwj` generator runs on UTF-16 code units: without u/v a supplementary emoji is two surrogates, so a ZWJ join is [low-of-before, ZWJ, high-of-after], and consecutive joins coalesce only across a single BMP unit — NOT across a supplementary emoji (whose two surrogates differ). We keep emoji as one Character, so `/[👨👩👦]/` was reported as one joined sequence instead of two. Both code paths now branch on the u/v flag: - without u/v: emit one zwj per join when separated by a supplementary char; the literal path reports at the emoji's UTF-8 midpoint (start+2) to model the surrogate boundary, and the call path lets the source map collapse those onto the original escape. - with u/v: every char is one code point, so consecutive joins coalesce as before. _offsetToLineCol now counts one UTF-16 unit when a span endpoint lands inside a 4-byte char (the surrogate-midpoint offsets above). No existing rule reports mid-codepoint, so this is inert elsewhere. no-misleading-character-class 168/174 -> 170/174 (cases 95 literal, 143 escaped). Hybrid 147 -> 141 gaps, 0 regressions.
…ty harness The runner compensates for TS syntax in mis-marked `.js` files by re-parsing as `ts` when the `js` parse errors and `ts` is clean — but only for `parseLang === "js"`. Cases with `ecmaFeatures.jsx` get `parseLang === "jsx"`, so `abstract class`/`public` (whose ESLint-test oracle used @typescript-eslint/parser) produced a malformed jsx AST with ErrorNodes, and `indent` (the most AST-sensitive rule) false-positived across the whole method body. Extend the upgrade to jsx→tsx so the runner parses these the way the oracle did. indent 1079/1090 -> 1085/1090 (6 cases: the abstract-class/accessibility-modifier group). Hybrid 141 -> 135 gaps, 0 regressions. Also adds tests/differential/estree-fidelity.js — a diff harness comparing ez's ESTree (type + range, per node) against @typescript-eslint/parser for a snippet. It pinned this down (identical AST; only the malformed-vs-clean parse differed) and is reusable for future parser-fidelity work (the remaining indent JSX cases).
…ly fully-clean) The TS-syntax-in-`.js` re-parse only adopted the TS AST when it had ZERO ErrorNodes, but the code comment documents the intent as "strictly fewer ErrorNodes than JS" — and computed `_jsErrCount` was then unused. Cases where TS is much cleaner but not perfect (e.g. object-shorthand's avoidExplicitReturnArrows test: 69 JS ErrorNodes vs 1 TS ErrorNode from a `(void)` parenthesized return type) kept the malformed 69-error JS AST, so the rule saw garbage. Adopt the TS AST when `_tsErrCount < _jsErrCount`, matching the comment and the parser the oracle used (@typescript-eslint/parser). object-shorthand 18 FN -> 6 FN (the 6 remaining are the `(void)` rows, filed as es-parser #62 — a parenthesized arrow return type becomes an ErrorNode). Full corpus: 135 gaps unchanged at the case level, 0 regressions. Context: while checking es-parser #49 (arrow returnType) — that adapter fix is already in (ArrowFunctionExpression.returnType is exposed); the real object-shorthand blocker was this upgrade condition plus the #62 parse gap.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes a large batch of differential-conformance gaps in hybrid mode (
EZ_RUN_NATIVE=1): 322 → 153 hybrid gaps, zero regressions. Each step was validated against the full corpus, and the per-rule regression gate stayed clean.Changes
parserServices type-info gating (
js/eslint-runner.js) — the biggest single win. The light parserServices was exposing a non-nullprogramand the TS node maps unconditionally, so type-aware plugin rules (sonarjs/*, eslint-plugin-n) ran and fired on cases where the oracle — which has noprojectService/projectconfigured — bails viaisRequiredParserServicesand produces nothing. Now gated on a new_typeInfoRequestedflag:programis null when no type info was requested → type-aware rules bail like the oracle.@typescript-eslint/parser, which always supplies the syntactic maps with a null program), so rules likenaming-conventionare unaffected.undefinedflag (LSP / non-differential entry points) preserves prior behavior.This cleared the sonarjs FP cluster:
unused-named-groups,new-operator-misuse,post-message,no-alphabetical-sort,non-number-in-arithmetic-expression,in-operator-type-error. Two regressions it surfaced (n/no-sync,naming-convention) were caught by the regression gate and fixed via the JS-language condition. Promoted@typescript-eslintfacade rules are unaffected (their fixtures are 100%projectService-configured).no-confusing-void-expression52/107 → 107/107.consolewas wrongly listed inBUILTIN_ES2015_GLOBALS— it's a host/env global, not an ECMAScript builtin (the JS side already has it in_ENV_GLOBALS). Pre-declaring it as an implicit-global symbol shadowed ez-checker's curatedConsoletype, soconsole.log(...)typed asanyinstead ofvoid. Removed it; unresolvedconsolestill reports as a global ref viaisGlobalReference.strict-boolean-expressions185/212 → 208/212.walkTypenow follows a generic type parameter's constraint (T extends boolean→ boolean) instead of treating it asany, matchinggetConstrainedTypeAtLocation.no-unnecessary-condition— 16noOverlapBooleanExpressionfalse negatives closed.involvesIndeterminateType/typesCanOverlapnow resolve type params to their constraint, so<T extends object>(a: T)compared againstnull/undefinedfires correctly.consistent-type-imports16 → 4 gaps. Detectexport type { X }(es-parser emitsread, nottype_read, for those specifiers) viasymbolRefsAllInTypeExports. Suppress the JSX-factory false positive (es-parser omits the implicit factory reference) only for the precise candidate: a default import whose name matches the JSX pragma (defaultReact; honorssettings.react.pragma) in a file that actually contains JSX elements — so a genuinely type-only default import, or any import in a JSX-free file, is still reported.no-unnecessary-type-conversion— type-param constraint handling intypeIsBigIntish.Upstream blockers filed
Several remaining gaps are blocked on the sibling repos and are tracked there: ez-checker #21/#22/#23 (implicit-global shadowing, Infinity/NaN, type-param constraints) and es-parser #33/#34/#49/#53 (export-type refs, JSX-factory ref, arrow
returnTypeon the ESTree node, parameter-type-annotation scoping). The Ez-side workarounds in this PR cover what can be fixed without those.Validation
zig build napibuilds clean; baseline updated.Test plan
Expect:
Corpus hybrid: 81123/81276 pass (99.8%), 153 gaps … No regressions.Update — additional commit:
no-duplicate-type-constituents79/82 → 82/82.type T = A | Awith undeclaredAwas wrongly flagged; an undeclared name is the TS error type (two are non-equivalent).typeNodeIsErrornow treats a type_reference whose name is neither a known type nor an in-scope type parameter as the error type — type params (f<T>(): T | T) and lib/user types still flag. Hybrid now 150 gaps.Remaining gaps are the hard tail:
indent(45 FP, whitespace),object-shorthand(es-parser #49),no-unsafe-call/restrict-plus-operands(ez-checker #21 / #24),no-misleading-character-class(Unicode + const-string resolution),no-shadow(scope-manager FE/CE-name representation), and CFG-based rules.Update — additional commit:
no-misleading-character-class165/174 → 168/174. Resolve const-string pattern/flags variables (const p = "[👍]"; new RegExp(p),const flags = ""; new RegExp("[\u{1f44d}]", flags)) viaconstInitializerOf. CallSourceMap gains anoverridespan so diagnostics report on the argument node (ESLint can't map char offsets through a resolved variable). Hybrid now 147 gaps. Remaining no-misleading gaps are unrelated sub-issues (surrogate-pair ranges, ZWJ-sequence off-by-one, template-substitution evaluation, v-flag nested classes).Update — additional commit:
no-misleading-character-class168/174 → 170/174 — correct ZWJ-sequence diagnostics. ESLint'szwjruns on UTF-16 code units, so/[👨👩👦]/is two joins (one per ZWJ), not one coalesced sequence. Both code paths now branch on the u/v flag and model the surrogate boundary at the emoji's UTF-8 midpoint (literal path) or via the source map (escaped path);_offsetToLineColcounts one unit for a span endpoint inside a 4-byte char (inert for existing rules). Hybrid now 141 gaps.Update —
indentCat A + fidelity harness: Builttests/differential/estree-fidelity.js(per-node type+range diff vs @typescript-eslint/parser) and used it to root-cause theindentgaps. The TS-class cases (abstract class+public) weren't an indent-algorithm problem at all — the runner's TS-syntax-in-.jsre-parse only upgradedjs→ts, neverjsx→tsx, so jsx-enabled cases linted against a malformed (ErrorNode) AST. Extended the upgrade to jsx→tsx.indent1079→1085/1090; hybrid now 135 gaps. Remaining indent: Cat C (es-parser let-ASI #59) and Cat B (JSX fidelity).Update — checked es-parser #49 (object-shorthand): #49 (arrow
returnTypenot exposed) is already resolved — the adapter exposesArrowFunctionExpression.returnType. The actualavoidExplicitReturnArrowsFNs were two other things: (1) the js→ts re-parse only adopted the TS AST when fully clean, but the code comment says "strictly fewer ErrorNodes" — fixed (object-shorthand 18→6 FN); (2) the remaining 6 are a parenthesized arrow return type ((): (void) =>) becoming an ErrorNode, filed as es-parser #62.