parseman 0.46.0 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/dist/analysis/choice-cost-render.d.ts +12 -4
- package/dist/analysis/choice-cost-render.d.ts.map +1 -1
- package/dist/analysis/choice-cost.d.ts +2 -2
- package/dist/analysis/choice-cost.d.ts.map +1 -1
- package/dist/analysis/commitment.d.ts +131 -0
- package/dist/analysis/commitment.d.ts.map +1 -0
- package/dist/analysis/diagnostics.cjs +9929 -7465
- package/dist/analysis/diagnostics.cjs.map +3 -4
- package/dist/analysis/diagnostics.d.ts +2 -2
- package/dist/analysis/diagnostics.js +9929 -7465
- package/dist/analysis/diagnostics.js.map +3 -4
- package/dist/analysis/duplication.d.ts.map +1 -1
- package/dist/analysis/fix.d.ts +3 -3
- package/dist/analysis/fix.d.ts.map +1 -1
- package/dist/analysis/format-number.d.ts +23 -0
- package/dist/analysis/format-number.d.ts.map +1 -0
- package/dist/analysis/gating.d.ts +39 -2
- package/dist/analysis/gating.d.ts.map +1 -1
- package/dist/analysis/rebuild.d.ts.map +1 -1
- package/dist/analysis/terminal.d.ts +5 -2
- package/dist/analysis/terminal.d.ts.map +1 -1
- package/dist/cli/index.cjs +10865 -8535
- package/dist/cli/index.cjs.map +3 -4
- package/dist/cli/index.js +10865 -8535
- package/dist/cli/index.js.map +3 -4
- package/dist/combinators/adjacency.d.ts +100 -0
- package/dist/combinators/adjacency.d.ts.map +1 -0
- package/dist/combinators/choice.d.ts.map +1 -1
- package/dist/combinators/completions.d.ts.map +1 -1
- package/dist/combinators/dispatch.d.ts +8 -1
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/gate.d.ts.map +1 -1
- package/dist/combinators/grammar.d.ts +22 -0
- package/dist/combinators/grammar.d.ts.map +1 -1
- package/dist/combinators/literal.d.ts +8 -0
- package/dist/combinators/literal.d.ts.map +1 -1
- package/dist/combinators/map.d.ts +0 -1
- package/dist/combinators/map.d.ts.map +1 -1
- package/dist/combinators/node.d.ts +13 -3
- package/dist/combinators/node.d.ts.map +1 -1
- package/dist/combinators/parser.d.ts +1 -1
- package/dist/combinators/parser.d.ts.map +1 -1
- package/dist/combinators/ref.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts +1 -16
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/repeat.d.ts +30 -2
- package/dist/combinators/repeat.d.ts.map +1 -1
- package/dist/combinators/scanTo.d.ts +71 -1
- package/dist/combinators/scanTo.d.ts.map +1 -1
- package/dist/combinators/sequence.d.ts.map +1 -1
- package/dist/combinators/token.d.ts.map +1 -1
- package/dist/combinators/trivia-boundary.d.ts +26 -0
- package/dist/combinators/trivia-boundary.d.ts.map +1 -0
- package/dist/combinators/trivia-skip.d.ts +40 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/combinators/withCtx.d.ts.map +1 -1
- package/dist/compiler/compile-linkable-table.d.ts +104 -0
- package/dist/compiler/compile-linkable-table.d.ts.map +1 -0
- package/dist/compiler/degradation.d.ts +0 -2
- package/dist/compiler/degradation.d.ts.map +1 -1
- package/dist/compiler/fields.d.ts +13 -0
- package/dist/compiler/fields.d.ts.map +1 -1
- package/dist/compiler/grammar-coverage-ids.d.ts +5 -5
- package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +47 -53
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/compiler/token-alphabet.d.ts +81 -0
- package/dist/compiler/token-alphabet.d.ts.map +1 -0
- package/dist/compiler/token-dispatch.d.ts +95 -0
- package/dist/compiler/token-dispatch.d.ts.map +1 -0
- package/dist/compiler/token-scanner.d.ts +53 -0
- package/dist/compiler/token-scanner.d.ts.map +1 -0
- package/dist/compiler/value-usage.d.ts.map +1 -1
- package/dist/coverage.d.ts +1 -1
- package/dist/coverage.d.ts.map +1 -1
- package/dist/cst/capture-buffer.d.ts +35 -0
- package/dist/cst/capture-buffer.d.ts.map +1 -1
- package/dist/cst/reflection-attach.d.ts +8 -0
- package/dist/cst/reflection-attach.d.ts.map +1 -0
- package/dist/cst/reflection-symbols.d.ts +10 -0
- package/dist/cst/reflection-symbols.d.ts.map +1 -0
- package/dist/cst/reflection.d.ts +2 -4
- package/dist/cst/reflection.d.ts.map +1 -1
- package/dist/cst/root-trivia-scope.d.ts +26 -0
- package/dist/cst/root-trivia-scope.d.ts.map +1 -0
- package/dist/cst/trivia-charscan.d.ts +18 -0
- package/dist/cst/trivia-charscan.d.ts.map +1 -0
- package/dist/cst/trivia-kinds.d.ts +19 -0
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/functional/doc.d.ts.map +1 -1
- package/dist/functional/run-tabled.d.ts +12 -0
- package/dist/functional/run-tabled.d.ts.map +1 -0
- package/dist/functional/run.d.ts +25 -42
- package/dist/functional/run.d.ts.map +1 -1
- package/dist/grammar-metadata.d.ts +3 -0
- package/dist/grammar-metadata.d.ts.map +1 -0
- package/dist/index.cjs +9224 -7203
- package/dist/index.cjs.map +3 -4
- package/dist/index.d.ts +18 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9219 -7198
- package/dist/index.js.map +3 -4
- package/dist/language-service/index.cjs +311 -121
- package/dist/language-service/index.cjs.map +3 -4
- package/dist/language-service/index.js +311 -121
- package/dist/language-service/index.js.map +3 -4
- package/dist/oracle/index.cjs.map +0 -1
- package/dist/oracle/index.js.map +0 -1
- package/dist/parse-context.d.ts +49 -0
- package/dist/parse-context.d.ts.map +1 -0
- package/dist/plugin/evaluator.d.ts +40 -1
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.cjs +11146 -9593
- package/dist/plugin/index.cjs.map +3 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +11146 -9593
- package/dist/plugin/index.js.map +3 -4
- package/dist/regex/classes.d.ts +28 -0
- package/dist/regex/classes.d.ts.map +1 -1
- package/dist/regex/first-set.d.ts +14 -0
- package/dist/regex/first-set.d.ts.map +1 -1
- package/dist/run/index.cjs +136 -51
- package/dist/run/index.cjs.map +2 -3
- package/dist/run/index.js +136 -51
- package/dist/run/index.js.map +2 -3
- package/dist/spec/index.cjs +2898 -1623
- package/dist/spec/index.cjs.map +3 -4
- package/dist/spec/index.js +2898 -1623
- package/dist/spec/index.js.map +3 -4
- package/dist/spec/model.d.ts +8 -0
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/table/assemble.d.ts +307 -0
- package/dist/table/assemble.d.ts.map +1 -0
- package/dist/table/cell.d.ts +42 -0
- package/dist/table/cell.d.ts.map +1 -0
- package/dist/table/child-slots.d.ts +30 -0
- package/dist/table/child-slots.d.ts.map +1 -0
- package/dist/table/compile-rule-map.d.ts +136 -0
- package/dist/table/compile-rule-map.d.ts.map +1 -0
- package/dist/table/compile.d.ts +56 -0
- package/dist/table/compile.d.ts.map +1 -0
- package/dist/table/duplication-hook.d.ts +32 -0
- package/dist/table/duplication-hook.d.ts.map +1 -0
- package/dist/table/emit-assembly.d.ts +139 -0
- package/dist/table/emit-assembly.d.ts.map +1 -0
- package/dist/table/emit.d.ts +113 -0
- package/dist/table/emit.d.ts.map +1 -0
- package/dist/table/encode-baseline.d.ts +16 -0
- package/dist/table/encode-baseline.d.ts.map +1 -0
- package/dist/table/encode.d.ts +71 -0
- package/dist/table/encode.d.ts.map +1 -0
- package/dist/table/exec-baseline.d.ts +9 -0
- package/dist/table/exec-baseline.d.ts.map +1 -0
- package/dist/table/exec.d.ts +72 -0
- package/dist/table/exec.d.ts.map +1 -0
- package/dist/table/fold.d.ts +25 -0
- package/dist/table/fold.d.ts.map +1 -0
- package/dist/table/index.cjs +11375 -0
- package/dist/table/index.cjs.map +6 -0
- package/dist/table/index.d.ts +100 -0
- package/dist/table/index.d.ts.map +1 -0
- package/dist/table/index.js +11352 -0
- package/dist/table/index.js.map +6 -0
- package/dist/table/inspect.d.ts +17 -0
- package/dist/table/inspect.d.ts.map +1 -0
- package/dist/table/ops.d.ts +446 -0
- package/dist/table/ops.d.ts.map +1 -0
- package/dist/table/program.d.ts +493 -0
- package/dist/table/program.d.ts.map +1 -0
- package/dist/table/run-support.d.ts +38 -0
- package/dist/table/run-support.d.ts.map +1 -0
- package/dist/{compiler/scannable-run.d.ts → table/scan-shapes.d.ts} +14 -13
- package/dist/table/scan-shapes.d.ts.map +1 -0
- package/dist/table/site-labels.d.ts +58 -0
- package/dist/table/site-labels.d.ts.map +1 -0
- package/dist/table/stamp.d.ts +70 -0
- package/dist/table/stamp.d.ts.map +1 -0
- package/dist/types.d.ts +80 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +25 -2
- package/src/analysis/choice-cost-gate.ts +359 -0
- package/src/analysis/choice-cost-render.ts +293 -0
- package/src/analysis/choice-cost.ts +1008 -0
- package/src/analysis/commitment.ts +557 -0
- package/src/analysis/corpus.ts +130 -0
- package/src/analysis/diagnose-render.ts +625 -0
- package/src/analysis/diagnose.ts +317 -0
- package/src/analysis/diagnostics.ts +33 -0
- package/src/analysis/duplication.ts +1955 -0
- package/src/analysis/fix-render.ts +217 -0
- package/src/analysis/fix.ts +669 -0
- package/src/analysis/format-number.ts +31 -0
- package/src/analysis/gating.ts +792 -0
- package/src/analysis/grammar.ts +72 -0
- package/src/analysis/rebuild.ts +287 -0
- package/src/analysis/terminal.ts +336 -0
- package/src/cli/index.ts +413 -0
- package/src/combinators/adjacency.ts +171 -0
- package/src/combinators/attempt.ts +41 -0
- package/src/combinators/case-fold.ts +90 -0
- package/src/combinators/choice.ts +466 -0
- package/src/combinators/completions.ts +64 -0
- package/src/combinators/dispatch.ts +358 -0
- package/src/combinators/expect.ts +159 -0
- package/src/combinators/first-set.ts +322 -0
- package/src/combinators/gate.ts +50 -0
- package/src/combinators/grammar.ts +272 -0
- package/src/combinators/keywords.ts +145 -0
- package/src/combinators/literal.ts +102 -0
- package/src/combinators/map.ts +106 -0
- package/src/combinators/node.ts +345 -0
- package/src/combinators/not.ts +55 -0
- package/src/combinators/parser.ts +266 -0
- package/src/combinators/peek.ts +63 -0
- package/src/combinators/probe.ts +25 -0
- package/src/combinators/ref.ts +109 -0
- package/src/combinators/regex.ts +170 -0
- package/src/combinators/repeat.ts +561 -0
- package/src/combinators/scanTo.ts +393 -0
- package/src/combinators/sequence.ts +221 -0
- package/src/combinators/token.ts +120 -0
- package/src/combinators/trivia-boundary.ts +48 -0
- package/src/combinators/trivia-skip.ts +821 -0
- package/src/combinators/withCtx.ts +45 -0
- package/src/compiler/build-arity.ts +330 -0
- package/src/compiler/compile-linkable-table.ts +209 -0
- package/src/compiler/degradation.ts +266 -0
- package/src/compiler/fields.ts +191 -0
- package/src/compiler/grammar-coverage-ids.ts +129 -0
- package/src/compiler/ir-serialize.ts +615 -0
- package/src/compiler/linker.ts +954 -0
- package/src/compiler/token-alphabet.ts +209 -0
- package/src/compiler/token-dispatch.ts +353 -0
- package/src/compiler/token-scanner.ts +318 -0
- package/src/compiler/value-usage.ts +147 -0
- package/src/coverage.ts +383 -0
- package/src/cst/capture-buffer.ts +297 -0
- package/src/cst/host-mode.ts +69 -0
- package/src/cst/reflection-attach.ts +11 -0
- package/src/cst/reflection-symbols.ts +9 -0
- package/src/cst/reflection.ts +149 -0
- package/src/cst/relative-spans.ts +207 -0
- package/src/cst/root-trivia-scope.ts +30 -0
- package/src/cst/trivia-charscan.ts +312 -0
- package/src/cst/trivia-entries.ts +285 -0
- package/src/cst/trivia-index.ts +131 -0
- package/src/cst/trivia-kinds.ts +358 -0
- package/src/cst/types.ts +76 -0
- package/src/cst/walk.ts +101 -0
- package/src/functional/doc.ts +959 -0
- package/src/functional/run-tabled.ts +153 -0
- package/src/functional/run.ts +558 -0
- package/src/grammar-metadata.ts +2 -0
- package/src/index.ts +189 -0
- package/src/language-service/index.ts +221 -0
- package/src/line-index.ts +131 -0
- package/src/oracle/digest.ts +620 -0
- package/src/oracle/index.ts +55 -0
- package/src/parse-context.ts +91 -0
- package/src/plugin/direct-builder-static.ts +102 -0
- package/src/plugin/evaluator.ts +1505 -0
- package/src/plugin/free-identifiers.ts +338 -0
- package/src/plugin/index.ts +2281 -0
- package/src/plugin/reducer-resolver.ts +606 -0
- package/src/recovery/scan.ts +155 -0
- package/src/regex/classes.ts +183 -0
- package/src/regex/first-set.ts +316 -0
- package/src/run/index.ts +31 -0
- package/src/spec/ebnf.ts +128 -0
- package/src/spec/index.ts +54 -0
- package/src/spec/model.ts +636 -0
- package/src/spec/railroad-lib.ts +559 -0
- package/src/spec/railroad.ts +256 -0
- package/src/table/assemble.ts +2994 -0
- package/src/table/cell.ts +42 -0
- package/src/table/child-slots.ts +113 -0
- package/src/table/compile-rule-map.ts +336 -0
- package/src/table/compile.ts +244 -0
- package/src/table/duplication-hook.ts +89 -0
- package/src/table/emit-assembly.ts +1766 -0
- package/src/table/emit.ts +418 -0
- package/src/table/encode-baseline.ts +340 -0
- package/src/table/encode.ts +1569 -0
- package/src/table/exec-baseline.ts +537 -0
- package/src/table/exec.ts +1672 -0
- package/src/table/fold.ts +38 -0
- package/src/table/index.ts +108 -0
- package/src/table/inspect.ts +52 -0
- package/src/table/ops.ts +459 -0
- package/src/table/program.ts +855 -0
- package/src/table/run-support.ts +66 -0
- package/src/table/scan-shapes.ts +1578 -0
- package/src/table/site-labels.ts +228 -0
- package/src/table/stamp.ts +229 -0
- package/src/types.ts +563 -0
- package/src/version.ts +16 -0
- package/dist/compiler/codegen.d.ts +0 -334
- package/dist/compiler/codegen.d.ts.map +0 -1
- package/dist/compiler/inline-build.d.ts +0 -11
- package/dist/compiler/inline-build.d.ts.map +0 -1
- package/dist/compiler/inline-callback.d.ts +0 -21
- package/dist/compiler/inline-callback.d.ts.map +0 -1
- package/dist/compiler/line-index.d.ts +0 -2
- package/dist/compiler/line-index.d.ts.map +0 -1
- package/dist/compiler/module-hoist.d.ts +0 -106
- package/dist/compiler/module-hoist.d.ts.map +0 -1
- package/dist/compiler/scannable-run.d.ts.map +0 -1
- package/dist/compiler/scannable-terminal.d.ts +0 -23
- package/dist/compiler/scannable-terminal.d.ts.map +0 -1
- package/dist/compiler/trivia-fast-path.d.ts +0 -38
- package/dist/compiler/trivia-fast-path.d.ts.map +0 -1
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emit-time scope check: does the module parseman is about to hand back name
|
|
3
|
+
* anything that nothing binds?
|
|
4
|
+
*
|
|
5
|
+
* Lowering DELETES code — the `rules(...)` call sites, the `x.define(...)`
|
|
6
|
+
* statements, and finally the macro import itself. Every one of those deletions
|
|
7
|
+
* is only safe because the text that referenced the deleted binding was deleted
|
|
8
|
+
* too. When a shape slips through where it wasn't, the artifact still builds,
|
|
9
|
+
* still imports, and throws `ReferenceError` the first time a consumer touches
|
|
10
|
+
* the binding — in the consumer's process, long after publish.
|
|
11
|
+
*
|
|
12
|
+
* The specific shapes are refused where they are recognised (an exported
|
|
13
|
+
* `rules()` factory is thrown on in index.ts before we ever get here). This is
|
|
14
|
+
* the net beneath them: it does not know or care HOW a name escaped, only that
|
|
15
|
+
* the emitted module reads it and nothing in the module binds it.
|
|
16
|
+
*
|
|
17
|
+
* Two deliberate design points:
|
|
18
|
+
*
|
|
19
|
+
* 1. SCOPE ANALYSIS, NOT GREP. A text scan for the macro names reports `node`,
|
|
20
|
+
* `rule`, `not` and `field` in hand-written host modules — they are ordinary
|
|
21
|
+
* parameter and property names — and a check that cries wolf is a check
|
|
22
|
+
* people learn to pass with a comment. Only an identifier in a READ position
|
|
23
|
+
* with no binding counts.
|
|
24
|
+
*
|
|
25
|
+
* 2. FLAT BINDINGS, NOT A SCOPE TREE. A name is considered bound if ANYTHING in
|
|
26
|
+
* the module binds it, in any scope. That is deliberately weaker than real
|
|
27
|
+
* scope resolution: it cannot see a name that is bound in a sibling scope and
|
|
28
|
+
* free in this one. It is also structurally incapable of the false positive
|
|
29
|
+
* that would make this check worthless, and the failure it exists to catch —
|
|
30
|
+
* a name whose ONLY binding was the deleted import — is caught exactly.
|
|
31
|
+
*
|
|
32
|
+
* Build-time only. Nothing here runs in, or is emitted into, the artifact.
|
|
33
|
+
*/
|
|
34
|
+
import { parseSync } from 'oxc-parser'
|
|
35
|
+
|
|
36
|
+
export type FreeIdentifier = {
|
|
37
|
+
/** The identifier that is read but bound by nothing. */
|
|
38
|
+
name: string
|
|
39
|
+
/** 1-based line in the EMITTED module. */
|
|
40
|
+
line: number
|
|
41
|
+
/** 1-based column in the emitted module. */
|
|
42
|
+
column: number
|
|
43
|
+
/**
|
|
44
|
+
* The top-level declaration the read sits inside — `export const cssFactory`,
|
|
45
|
+
* `function build`, … — or null if it is at statement level. This is what turns
|
|
46
|
+
* "free identifier `sequence`" into a place to go and look.
|
|
47
|
+
*/
|
|
48
|
+
enclosing: string | null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Per-top-level-statement facts, for the reachability filter. */
|
|
52
|
+
type TopLevel = {
|
|
53
|
+
/** Names this statement binds at module level. */
|
|
54
|
+
declared: Set<string>
|
|
55
|
+
/** `export`ed, so the module system can reach it however unreferenced it looks. */
|
|
56
|
+
exported: boolean
|
|
57
|
+
/** A binding whose value is only produced by CALLING it — a function/arrow initializer. */
|
|
58
|
+
declaresOnlyFunctions: boolean
|
|
59
|
+
label: string | null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type AnyNode = { type: string } & Record<string, unknown>
|
|
63
|
+
|
|
64
|
+
const isNode = (v: unknown): v is AnyNode =>
|
|
65
|
+
typeof v === 'object' && v !== null && typeof (v as { type?: unknown }).type === 'string'
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Type-land keys. The emitted module is TypeScript, and a type annotation names
|
|
69
|
+
* types — `Combinator`, `Record`, `HostMode` — which are not value bindings and
|
|
70
|
+
* must never be read as references.
|
|
71
|
+
*/
|
|
72
|
+
const TYPE_KEYS = new Set([
|
|
73
|
+
'typeAnnotation', 'returnType', 'typeParameters', 'typeArguments',
|
|
74
|
+
'superTypeArguments', 'typeName', 'implements', 'extends', 'accessibility',
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* TS wrapper nodes that DO contain a real value expression. Everything else whose
|
|
79
|
+
* type starts with `TS` is pure type-land and is skipped whole.
|
|
80
|
+
*/
|
|
81
|
+
const TS_VALUE_WRAPPERS = new Set([
|
|
82
|
+
'TSAsExpression', 'TSSatisfiesExpression', 'TSNonNullExpression',
|
|
83
|
+
'TSInstantiationExpression', 'TSTypeAssertion',
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Names that are legitimately free in a module: the host environment provides
|
|
88
|
+
* them. Taken from the live `globalThis` rather than a hand-kept list — the check
|
|
89
|
+
* runs in the build's own Node process, so this is the set that actually exists —
|
|
90
|
+
* plus the implicit bindings and the browser/CJS names a Node build host lacks but
|
|
91
|
+
* a published artifact may legitimately expect.
|
|
92
|
+
*/
|
|
93
|
+
const HOST_GLOBALS = new Set<string>([
|
|
94
|
+
...Object.getOwnPropertyNames(globalThis),
|
|
95
|
+
'undefined', 'NaN', 'Infinity', 'globalThis', 'arguments',
|
|
96
|
+
'window', 'document', 'self', 'navigator', 'location', 'top', 'parent',
|
|
97
|
+
'require', 'module', 'exports', '__dirname', '__filename',
|
|
98
|
+
])
|
|
99
|
+
|
|
100
|
+
/** Collect every name the pattern BINDS. Default values inside it are references, not bindings. */
|
|
101
|
+
function bindPattern(pattern: unknown, out: Set<string>): void {
|
|
102
|
+
if (!isNode(pattern)) return
|
|
103
|
+
switch (pattern.type) {
|
|
104
|
+
case 'Identifier':
|
|
105
|
+
case 'BindingIdentifier':
|
|
106
|
+
out.add(String(pattern.name))
|
|
107
|
+
return
|
|
108
|
+
case 'ObjectPattern':
|
|
109
|
+
for (const prop of (pattern.properties as unknown[]) ?? []) {
|
|
110
|
+
if (!isNode(prop)) continue
|
|
111
|
+
if (prop.type === 'RestElement' || prop.type === 'BindingRestElement') bindPattern(prop.argument, out)
|
|
112
|
+
else bindPattern(prop.value, out)
|
|
113
|
+
}
|
|
114
|
+
return
|
|
115
|
+
case 'ArrayPattern':
|
|
116
|
+
for (const el of (pattern.elements as unknown[]) ?? []) bindPattern(el, out)
|
|
117
|
+
return
|
|
118
|
+
case 'AssignmentPattern':
|
|
119
|
+
bindPattern(pattern.left, out)
|
|
120
|
+
return
|
|
121
|
+
case 'RestElement':
|
|
122
|
+
case 'BindingRestElement':
|
|
123
|
+
bindPattern(pattern.argument, out)
|
|
124
|
+
return
|
|
125
|
+
case 'TSParameterProperty':
|
|
126
|
+
bindPattern(pattern.parameter, out)
|
|
127
|
+
return
|
|
128
|
+
default:
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Every name bound ANYWHERE in the module, in any scope. See the header for why flat. */
|
|
134
|
+
function collectBindings(node: unknown, out: Set<string>): void {
|
|
135
|
+
if (Array.isArray(node)) {
|
|
136
|
+
for (const child of node) collectBindings(child, out)
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
if (!isNode(node)) return
|
|
140
|
+
|
|
141
|
+
switch (node.type) {
|
|
142
|
+
case 'ImportSpecifier':
|
|
143
|
+
case 'ImportDefaultSpecifier':
|
|
144
|
+
case 'ImportNamespaceSpecifier':
|
|
145
|
+
if (isNode(node.local)) out.add(String(node.local.name))
|
|
146
|
+
break
|
|
147
|
+
case 'VariableDeclarator':
|
|
148
|
+
bindPattern(node.id, out)
|
|
149
|
+
break
|
|
150
|
+
case 'FunctionDeclaration':
|
|
151
|
+
case 'FunctionExpression':
|
|
152
|
+
case 'TSDeclareFunction':
|
|
153
|
+
case 'ArrowFunctionExpression':
|
|
154
|
+
if (isNode(node.id)) out.add(String(node.id.name))
|
|
155
|
+
for (const p of ((node.params as AnyNode | undefined)?.items as unknown[] | undefined)
|
|
156
|
+
?? (node.params as unknown[] | undefined) ?? []) bindPattern(p, out)
|
|
157
|
+
if (isNode(node.params) && node.params.rest) bindPattern(node.params.rest, out)
|
|
158
|
+
break
|
|
159
|
+
case 'ClassDeclaration':
|
|
160
|
+
case 'ClassExpression':
|
|
161
|
+
if (isNode(node.id)) out.add(String(node.id.name))
|
|
162
|
+
break
|
|
163
|
+
case 'CatchClause':
|
|
164
|
+
if (node.param) {
|
|
165
|
+
// oxc wraps it: CatchParameter { pattern }. ESTree puts the pattern directly.
|
|
166
|
+
bindPattern(isNode(node.param) && node.param.pattern ? node.param.pattern : node.param, out)
|
|
167
|
+
}
|
|
168
|
+
break
|
|
169
|
+
default:
|
|
170
|
+
break
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (const key of Object.keys(node)) {
|
|
174
|
+
if (key === 'type' || key === 'start' || key === 'end') continue
|
|
175
|
+
collectBindings(node[key], out)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const FUNCTION_TYPES = new Set(['ArrowFunctionExpression', 'FunctionExpression', 'FunctionDeclaration'])
|
|
180
|
+
|
|
181
|
+
/** What a top-level statement declares, exports, and is called, for the diagnostic and the filter. */
|
|
182
|
+
function describeTopLevel(stmt: AnyNode): TopLevel {
|
|
183
|
+
const declared = new Set<string>()
|
|
184
|
+
let target: AnyNode = stmt
|
|
185
|
+
let prefix = ''
|
|
186
|
+
let exported = false
|
|
187
|
+
if (stmt.type === 'ExportNamedDeclaration' || stmt.type === 'ExportDefaultDeclaration') {
|
|
188
|
+
exported = true
|
|
189
|
+
prefix = 'export '
|
|
190
|
+
if (!isNode(stmt.declaration)) return { declared, exported, declaresOnlyFunctions: false, label: `${prefix}{ … }` }
|
|
191
|
+
target = stmt.declaration
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (target.type === 'VariableDeclaration') {
|
|
195
|
+
const decls = ((target.declarations as unknown[]) ?? []).filter(isNode)
|
|
196
|
+
for (const d of decls) bindPattern(d.id, declared)
|
|
197
|
+
const declaresOnlyFunctions = decls.length > 0
|
|
198
|
+
&& decls.every(d => isNode(d.init) && FUNCTION_TYPES.has(d.init.type))
|
|
199
|
+
const name = [...declared][0]
|
|
200
|
+
return {
|
|
201
|
+
declared, exported, declaresOnlyFunctions,
|
|
202
|
+
label: name ? `${prefix}${String(target.kind)} ${name}` : null,
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (target.type === 'FunctionDeclaration' && isNode(target.id)) {
|
|
206
|
+
declared.add(String(target.id.name))
|
|
207
|
+
return { declared, exported, declaresOnlyFunctions: true, label: `${prefix}function ${String(target.id.name)}` }
|
|
208
|
+
}
|
|
209
|
+
if (target.type === 'ClassDeclaration' && isNode(target.id)) {
|
|
210
|
+
declared.add(String(target.id.name))
|
|
211
|
+
return { declared, exported, declaresOnlyFunctions: false, label: `${prefix}class ${String(target.id.name)}` }
|
|
212
|
+
}
|
|
213
|
+
return { declared, exported, declaresOnlyFunctions: false, label: null }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Every identifier the emitted module READS but nothing binds.
|
|
218
|
+
*
|
|
219
|
+
* Returns an empty array when the module can't be parsed: a check that can't see
|
|
220
|
+
* the code has nothing to say about it, and refusing to emit on a parse failure
|
|
221
|
+
* would turn this into a second, worse syntax error.
|
|
222
|
+
*/
|
|
223
|
+
export function findFreeIdentifiers(code: string, filename: string): FreeIdentifier[] {
|
|
224
|
+
let parsed: ReturnType<typeof parseSync>
|
|
225
|
+
try {
|
|
226
|
+
parsed = parseSync(filename, code)
|
|
227
|
+
} catch {
|
|
228
|
+
return []
|
|
229
|
+
}
|
|
230
|
+
if (parsed.errors.length > 0) return []
|
|
231
|
+
|
|
232
|
+
const bound = new Set<string>()
|
|
233
|
+
collectBindings(parsed.program.body, bound)
|
|
234
|
+
|
|
235
|
+
// Line starts, so a byte offset becomes line:column without rescanning the file.
|
|
236
|
+
const lineStarts = [0]
|
|
237
|
+
for (let i = 0; i < code.length; i++) if (code.charCodeAt(i) === 10) lineStarts.push(i + 1)
|
|
238
|
+
const positionOf = (offset: number): { line: number; column: number } => {
|
|
239
|
+
let lo = 0
|
|
240
|
+
let hi = lineStarts.length - 1
|
|
241
|
+
while (lo < hi) {
|
|
242
|
+
const mid = (lo + hi + 1) >> 1
|
|
243
|
+
if (lineStarts[mid]! <= offset) lo = mid
|
|
244
|
+
else hi = mid - 1
|
|
245
|
+
}
|
|
246
|
+
return { line: lo + 1, column: offset - lineStarts[lo]! + 1 }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const statements = (parsed.program.body as unknown[]).filter(isNode)
|
|
250
|
+
const tops = statements.map(describeTopLevel)
|
|
251
|
+
|
|
252
|
+
type Candidate = FreeIdentifier & { stmt: number; insideFunction: boolean }
|
|
253
|
+
const candidates: Candidate[] = []
|
|
254
|
+
const seen = new Set<string>()
|
|
255
|
+
/** name -> indices of the top-level statements that READ it. Drives the reachability filter. */
|
|
256
|
+
const readIn = new Map<string, Set<number>>()
|
|
257
|
+
|
|
258
|
+
const visit = (node: unknown, stmt: number, insideFunction: boolean): void => {
|
|
259
|
+
if (Array.isArray(node)) {
|
|
260
|
+
for (const child of node) visit(child, stmt, insideFunction)
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
if (!isNode(node)) return
|
|
264
|
+
|
|
265
|
+
const type = node.type
|
|
266
|
+
|
|
267
|
+
// Import declarations bind; nothing in them reads.
|
|
268
|
+
if (type === 'ImportDeclaration' || type === 'ExportAllDeclaration') return
|
|
269
|
+
// `import.meta` / `new.target` — neither half is an identifier reference.
|
|
270
|
+
if (type === 'MetaProperty' || type === 'PrivateIdentifier') return
|
|
271
|
+
// `export { x } from './m'` re-exports; `x` is the OTHER module's name, not a local read.
|
|
272
|
+
if (type === 'ExportNamedDeclaration' && node.source) return
|
|
273
|
+
// Type-land. The wrappers carry a real expression; everything else is types only.
|
|
274
|
+
if (type.startsWith('TS')) {
|
|
275
|
+
if (TS_VALUE_WRAPPERS.has(type)) visit(node.expression, stmt, insideFunction)
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (type === 'Identifier' || type === 'IdentifierReference') {
|
|
280
|
+
const name = String(node.name)
|
|
281
|
+
let readers = readIn.get(name)
|
|
282
|
+
if (!readers) readIn.set(name, readers = new Set())
|
|
283
|
+
readers.add(stmt)
|
|
284
|
+
if (bound.has(name) || HOST_GLOBALS.has(name)) return
|
|
285
|
+
const offset = typeof node.start === 'number' ? node.start : 0
|
|
286
|
+
const { line, column } = positionOf(offset)
|
|
287
|
+
const key = `${name}@${line}:${column}`
|
|
288
|
+
if (seen.has(key)) return
|
|
289
|
+
seen.add(key)
|
|
290
|
+
candidates.push({ name, line, column, enclosing: tops[stmt]!.label, stmt, insideFunction })
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const nowInsideFunction = insideFunction || FUNCTION_TYPES.has(type)
|
|
295
|
+
|
|
296
|
+
for (const key of Object.keys(node)) {
|
|
297
|
+
if (key === 'type' || key === 'start' || key === 'end') continue
|
|
298
|
+
if (TYPE_KEYS.has(key)) continue
|
|
299
|
+
|
|
300
|
+
// A non-computed key is a NAME, not a read: `{ node: 1 }`, `x.field`, `class { rule() {} }`.
|
|
301
|
+
if (key === 'key' && node.computed !== true) continue
|
|
302
|
+
if (key === 'property' && node.computed !== true) continue
|
|
303
|
+
// Labels: `outer: for (…) { break outer }` — neither position is a value.
|
|
304
|
+
if (key === 'label') continue
|
|
305
|
+
// `export { local as exported }` — `exported` is a name in the export list.
|
|
306
|
+
if (key === 'exported') continue
|
|
307
|
+
// `import { imported as local }` is already returned above; belt and braces.
|
|
308
|
+
if (key === 'imported') continue
|
|
309
|
+
|
|
310
|
+
visit(node[key], stmt, nowInsideFunction)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
for (let i = 0; i < statements.length; i++) visit(statements[i], i, false)
|
|
315
|
+
|
|
316
|
+
/*
|
|
317
|
+
* REACHABILITY. A `const factory = (g) => ({ node(…) })` that lowering left as text is
|
|
318
|
+
* not a runtime failure and must not be reported: it is not exported, nothing else in
|
|
319
|
+
* the module names it, and its free names sit inside a function body that is therefore
|
|
320
|
+
* never entered — dead code a bundler drops and an unbundled module simply never
|
|
321
|
+
* evaluates. That is the documented two-artifact pattern (a LOCAL factory const), and
|
|
322
|
+
* it is exactly the contrast that makes the exported case fatal.
|
|
323
|
+
*
|
|
324
|
+
* All three conditions are load-bearing. Drop `insideFunction` and `const x = node(…)`
|
|
325
|
+
* at statement level stops being caught, though it throws the moment the module is
|
|
326
|
+
* imported. Drop the unreferenced test and a factory something still calls is missed.
|
|
327
|
+
* Drop `exported` and the shape 8995b1c refuses walks straight through.
|
|
328
|
+
*/
|
|
329
|
+
return candidates.filter(c => {
|
|
330
|
+
const top = tops[c.stmt]!
|
|
331
|
+
if (!c.insideFunction || top.exported || !top.declaresOnlyFunctions || top.declared.size === 0) return true
|
|
332
|
+
for (const name of top.declared) {
|
|
333
|
+
const readers = readIn.get(name)
|
|
334
|
+
if (readers && [...readers].some(i => i !== c.stmt)) return true
|
|
335
|
+
}
|
|
336
|
+
return false
|
|
337
|
+
}).map(({ name, line, column, enclosing }) => ({ name, line, column, enclosing }))
|
|
338
|
+
}
|