@telorun/analyzer 0.72.0 → 0.74.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/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +76 -20
- package/dist/artifact-axes.d.ts +14 -0
- package/dist/artifact-axes.d.ts.map +1 -0
- package/dist/artifact-axes.js +12 -0
- package/dist/artifact-layer-index.d.ts +2 -2
- package/dist/artifact-layer-index.d.ts.map +1 -1
- package/dist/artifact-layer-index.js +9 -4
- package/dist/artifact-selector.d.ts +47 -21
- package/dist/artifact-selector.d.ts.map +1 -1
- package/dist/artifact-selector.js +85 -16
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +144 -113
- package/dist/call-graph.d.ts +12 -1
- package/dist/call-graph.d.ts.map +1 -1
- package/dist/call-graph.js +22 -0
- package/dist/catch-scope.js +1 -1
- package/dist/cel-scope-query.d.ts.map +1 -1
- package/dist/cel-scope-query.js +32 -11
- package/dist/cel-scope.d.ts.map +1 -1
- package/dist/cel-scope.js +1 -0
- package/dist/eval-paths.d.ts +36 -5
- package/dist/eval-paths.d.ts.map +1 -1
- package/dist/eval-paths.js +47 -6
- package/dist/extends-resolution.d.ts +12 -0
- package/dist/extends-resolution.d.ts.map +1 -1
- package/dist/extends-resolution.js +23 -0
- package/dist/forwarded-declaration.d.ts +26 -0
- package/dist/forwarded-declaration.d.ts.map +1 -0
- package/dist/forwarded-declaration.js +31 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/kernel-globals.d.ts.map +1 -1
- package/dist/kernel-globals.js +21 -5
- package/dist/module-alias-scope.d.ts +11 -0
- package/dist/module-alias-scope.d.ts.map +1 -1
- package/dist/module-alias-scope.js +10 -0
- package/dist/module-file-claims.d.ts +17 -1
- package/dist/module-file-claims.d.ts.map +1 -1
- package/dist/module-file-claims.js +38 -3
- package/dist/module-named-files.d.ts +72 -0
- package/dist/module-named-files.d.ts.map +1 -0
- package/dist/module-named-files.js +115 -0
- package/dist/native-entries.d.ts +83 -0
- package/dist/native-entries.d.ts.map +1 -0
- package/dist/native-entries.js +169 -0
- package/dist/nearest-name.d.ts +22 -0
- package/dist/nearest-name.d.ts.map +1 -0
- package/dist/nearest-name.js +43 -0
- package/dist/normalize-inline-resources.d.ts +41 -13
- package/dist/normalize-inline-resources.d.ts.map +1 -1
- package/dist/normalize-inline-resources.js +236 -54
- package/dist/reference-field-map.d.ts +59 -1
- package/dist/reference-field-map.d.ts.map +1 -1
- package/dist/reference-field-map.js +200 -34
- package/dist/release/destinations.d.ts +59 -0
- package/dist/release/destinations.d.ts.map +1 -0
- package/dist/release/destinations.js +75 -0
- package/dist/release/index.d.ts +6 -2
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +3 -1
- package/dist/release/ledger.d.ts +20 -11
- package/dist/release/ledger.d.ts.map +1 -1
- package/dist/release/ledger.js +43 -14
- package/dist/release/payload-digest.d.ts +2 -1
- package/dist/release/payload-digest.d.ts.map +1 -1
- package/dist/release/payload-digest.js +2 -1
- package/dist/release/release-plan.d.ts +3 -2
- package/dist/release/release-plan.d.ts.map +1 -1
- package/dist/release/release-plan.js +12 -19
- package/dist/release/workspace-config.d.ts +114 -34
- package/dist/release/workspace-config.d.ts.map +1 -1
- package/dist/release/workspace-config.js +322 -38
- package/dist/release/workspace-schema.d.ts +41 -0
- package/dist/release/workspace-schema.d.ts.map +1 -0
- package/dist/release/workspace-schema.js +77 -0
- package/dist/resolve-ref-sentinels.d.ts.map +1 -1
- package/dist/resolve-ref-sentinels.js +8 -5
- package/dist/resolve-throws-union.d.ts +25 -4
- package/dist/resolve-throws-union.d.ts.map +1 -1
- package/dist/resolve-throws-union.js +62 -32
- package/dist/resolve-zone-requirements.d.ts.map +1 -1
- package/dist/resolve-zone-requirements.js +2 -1
- package/dist/rewrite-synthetic-origins.d.ts +6 -1
- package/dist/rewrite-synthetic-origins.d.ts.map +1 -1
- package/dist/rewrite-synthetic-origins.js +107 -13
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +18 -5
- package/dist/schema-error-report.d.ts +7 -0
- package/dist/schema-error-report.d.ts.map +1 -1
- package/dist/schema-error-report.js +9 -4
- package/dist/schema-walk.d.ts +47 -13
- package/dist/schema-walk.d.ts.map +1 -1
- package/dist/schema-walk.js +150 -30
- package/dist/scope-declarations.d.ts +59 -0
- package/dist/scope-declarations.d.ts.map +1 -0
- package/dist/scope-declarations.js +45 -0
- package/dist/source-entries.d.ts +119 -0
- package/dist/source-entries.d.ts.map +1 -0
- package/dist/source-entries.js +349 -0
- package/dist/step-bodies.d.ts +24 -0
- package/dist/step-bodies.d.ts.map +1 -0
- package/dist/step-bodies.js +28 -0
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/template-body.d.ts +10 -0
- package/dist/template-body.d.ts.map +1 -1
- package/dist/template-body.js +25 -0
- package/dist/validate-cel-context.d.ts +23 -8
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +51 -18
- package/dist/validate-exports.d.ts +28 -0
- package/dist/validate-exports.d.ts.map +1 -0
- package/dist/validate-exports.js +146 -0
- package/dist/validate-extends.d.ts.map +1 -1
- package/dist/validate-extends.js +37 -1
- package/dist/validate-module-artifact.d.ts +4 -2
- package/dist/validate-module-artifact.d.ts.map +1 -1
- package/dist/validate-module-artifact.js +43 -4
- package/dist/validate-native-entries.d.ts +15 -0
- package/dist/validate-native-entries.d.ts.map +1 -0
- package/dist/validate-native-entries.js +103 -0
- package/dist/validate-observed-state.d.ts +7 -1
- package/dist/validate-observed-state.d.ts.map +1 -1
- package/dist/validate-observed-state.js +24 -6
- package/dist/validate-provider-coherence.d.ts +4 -8
- package/dist/validate-provider-coherence.d.ts.map +1 -1
- package/dist/validate-provider-coherence.js +12 -123
- package/dist/validate-reference-forms.d.ts.map +1 -1
- package/dist/validate-reference-forms.js +2 -2
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +152 -65
- package/dist/validate-scope-reach.d.ts +31 -0
- package/dist/validate-scope-reach.d.ts.map +1 -0
- package/dist/validate-scope-reach.js +180 -0
- package/dist/validate-source-entries.d.ts +16 -0
- package/dist/validate-source-entries.d.ts.map +1 -0
- package/dist/validate-source-entries.js +90 -0
- package/dist/validate-template-body.d.ts +36 -0
- package/dist/validate-template-body.d.ts.map +1 -0
- package/dist/validate-template-body.js +246 -0
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +21 -39
- package/dist/value-type-keyword.d.ts +22 -5
- package/dist/value-type-keyword.d.ts.map +1 -1
- package/dist/value-type-keyword.js +16 -7
- package/package.json +4 -3
- package/src/analyzer.ts +97 -20
- package/src/artifact-axes.ts +24 -0
- package/src/artifact-layer-index.ts +9 -6
- package/src/artifact-selector.ts +110 -35
- package/src/builtins.ts +160 -113
- package/src/call-graph.ts +30 -0
- package/src/catch-scope.ts +1 -1
- package/src/cel-scope-query.ts +28 -9
- package/src/cel-scope.ts +1 -0
- package/src/eval-paths.ts +53 -6
- package/src/extends-resolution.ts +24 -0
- package/src/forwarded-declaration.ts +32 -0
- package/src/index.ts +37 -1
- package/src/kernel-globals.ts +28 -5
- package/src/migrations/CLAUDE.md +35 -0
- package/src/module-alias-scope.ts +17 -0
- package/src/module-file-claims.ts +63 -4
- package/src/module-named-files.ts +162 -0
- package/src/native-entries.ts +227 -0
- package/src/nearest-name.ts +47 -0
- package/src/normalize-inline-resources.ts +326 -70
- package/src/reference-field-map.ts +257 -52
- package/src/release/CLAUDE.md +32 -0
- package/src/release/destinations.ts +105 -0
- package/src/release/index.ts +33 -2
- package/src/release/ledger.ts +51 -21
- package/src/release/payload-digest.ts +2 -1
- package/src/release/release-plan.ts +16 -26
- package/src/release/workspace-config.ts +483 -47
- package/src/release/workspace-schema.ts +99 -0
- package/src/resolve-ref-sentinels.ts +7 -6
- package/src/resolve-throws-union.ts +87 -32
- package/src/resolve-zone-requirements.ts +2 -1
- package/src/rewrite-synthetic-origins.ts +107 -16
- package/src/schema-compat.ts +18 -4
- package/src/schema-error-report.ts +16 -4
- package/src/schema-walk.ts +162 -34
- package/src/scope-declarations.ts +98 -0
- package/src/source-entries.ts +502 -0
- package/src/step-bodies.ts +55 -0
- package/src/telo-version.ts +1 -1
- package/src/template-body.ts +25 -0
- package/src/validate-cel-context.ts +60 -18
- package/src/validate-exports.ts +185 -0
- package/src/validate-extends.ts +43 -0
- package/src/validate-module-artifact.ts +46 -3
- package/src/validate-native-entries.ts +147 -0
- package/src/validate-observed-state.ts +36 -7
- package/src/validate-provider-coherence.ts +17 -127
- package/src/validate-reference-forms.ts +2 -3
- package/src/validate-references.ts +151 -66
- package/src/validate-scope-reach.ts +213 -0
- package/src/validate-source-entries.ts +120 -0
- package/src/validate-template-body.ts +274 -0
- package/src/validate-throws-coverage.ts +24 -31
- package/src/value-type-keyword.ts +35 -9
- package/dist/validate-template-dispatch.d.ts +0 -27
- package/dist/validate-template-dispatch.d.ts.map +0 -1
- package/dist/validate-template-dispatch.js +0 -95
- package/src/validate-template-dispatch.ts +0 -99
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A module's **native files** — the `native:` block on a `Telo.Library` or
|
|
3
|
+
* `Telo.Application` doc, naming each platform-specific file the runtime does
|
|
4
|
+
* not import as a controller (a sidecar addon, a `dlopen`'d library, a
|
|
5
|
+
* per-platform data blob) once per platform tuple.
|
|
6
|
+
*
|
|
7
|
+
* ```yaml
|
|
8
|
+
* native:
|
|
9
|
+
* - name: better-sqlite3
|
|
10
|
+
* format: node
|
|
11
|
+
* os: linux
|
|
12
|
+
* arch: amd64
|
|
13
|
+
* libc: gnu
|
|
14
|
+
* abi: node-137
|
|
15
|
+
* path: ./native/linux-amd64-gnu-node-137/better_sqlite3.node
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Declared on the module doc rather than on a controller candidate, so the
|
|
19
|
+
* platform matrix is written once whatever the kind count, and the
|
|
20
|
+
* platform-neutral bundle ships once. Publish places each entry's file in the
|
|
21
|
+
* `native` layer of its selector (spec §1.2).
|
|
22
|
+
*
|
|
23
|
+
* `format` plus the platform axes build the same `ArtifactSelector` a
|
|
24
|
+
* controller candidate does, through the one selector normalizer. The declared
|
|
25
|
+
* `path` is the in-layer path — never flattened — because every layer of a
|
|
26
|
+
* module extracts into one directory, so paths must be disjoint per tuple.
|
|
27
|
+
*
|
|
28
|
+
* The single reader: the strict half is `validate-native-entries.ts`, and
|
|
29
|
+
* publish consumes the entries this returns. Browser-safe: string work only.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
ArtifactSelectorError,
|
|
34
|
+
PLATFORM_AXES,
|
|
35
|
+
selectorFromQualifiers,
|
|
36
|
+
type ArtifactSelector,
|
|
37
|
+
} from "./artifact-selector.js";
|
|
38
|
+
|
|
39
|
+
/** Every key an entry may carry. */
|
|
40
|
+
const KNOWN_KEYS = new Set<string>(["name", "format", "path", ...PLATFORM_AXES]);
|
|
41
|
+
|
|
42
|
+
/** Axes an entry must state: a native file is built for one platform. */
|
|
43
|
+
const REQUIRED_AXES = ["os", "arch"] as const;
|
|
44
|
+
|
|
45
|
+
/** The selector value grammar, applied to `name` as written. */
|
|
46
|
+
const NAME_TOKEN = /^[a-z0-9][a-z0-9_.-]*$/;
|
|
47
|
+
|
|
48
|
+
const GLOB_CHARS = /[*?[\]{}]/;
|
|
49
|
+
const URI_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
|
|
50
|
+
|
|
51
|
+
export interface NativeEntry {
|
|
52
|
+
/** The logical name controller code asks for. */
|
|
53
|
+
readonly name: string;
|
|
54
|
+
readonly selector: ArtifactSelector;
|
|
55
|
+
/** Module-root-relative POSIX path, normalized. */
|
|
56
|
+
readonly path: string;
|
|
57
|
+
/** Position in the `native:` list. */
|
|
58
|
+
readonly index: number;
|
|
59
|
+
/** Human-facing label for diagnostics: `native[0] ('better-sqlite3')`. */
|
|
60
|
+
readonly origin: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Why an entry could not be read.
|
|
65
|
+
*
|
|
66
|
+
* - `shape` — a missing, non-string or unknown key; the owner doc's JSON Schema
|
|
67
|
+
* reports these in the same analysis pass.
|
|
68
|
+
* - `invalid` — a value the schema accepts and the grammar rejects: an empty
|
|
69
|
+
* required string, a `name` or selector value outside the token grammar, an
|
|
70
|
+
* `abi` outside its value form, a path that names no single file.
|
|
71
|
+
* - `escape` — a `path` pointing outside the module directory.
|
|
72
|
+
*/
|
|
73
|
+
export interface NativeEntryProblem {
|
|
74
|
+
readonly kind: "shape" | "invalid" | "escape";
|
|
75
|
+
readonly index?: number;
|
|
76
|
+
readonly origin: string;
|
|
77
|
+
/** Dotted/bracketed path from the module doc root, for `data.path`. */
|
|
78
|
+
readonly path: string;
|
|
79
|
+
/** What is wrong, prefixed with `origin`. */
|
|
80
|
+
readonly message: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface NativeEntries {
|
|
84
|
+
readonly entries: NativeEntry[];
|
|
85
|
+
readonly problems: NativeEntryProblem[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type PathVerdict = { path: string } | { kind: "invalid" | "escape"; detail: string };
|
|
89
|
+
|
|
90
|
+
/** Normalize a module-relative path, deciding confinement from the written
|
|
91
|
+
* path alone: the module root is the one directory every path is measured
|
|
92
|
+
* from, so `..` below depth zero is an escape wherever the module sits. */
|
|
93
|
+
export function normalizeNativePath(raw: string): PathVerdict {
|
|
94
|
+
if (URI_SCHEME.test(raw) || raw.startsWith("/") || raw.startsWith("\\")) {
|
|
95
|
+
return {
|
|
96
|
+
kind: "escape",
|
|
97
|
+
detail: `path '${raw}' is not relative to the module root. A native file ships inside the module artifact, so name it relative to the directory holding telo.yaml.`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (GLOB_CHARS.test(raw)) {
|
|
101
|
+
return {
|
|
102
|
+
kind: "invalid",
|
|
103
|
+
detail: `path '${raw}' looks like a pattern. A native entry names exactly one file.`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const out: string[] = [];
|
|
107
|
+
for (const segment of raw.split(/[/\\]+/)) {
|
|
108
|
+
if (segment === "" || segment === ".") continue;
|
|
109
|
+
if (segment !== "..") {
|
|
110
|
+
out.push(segment);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (out.length === 0) {
|
|
114
|
+
return {
|
|
115
|
+
kind: "escape",
|
|
116
|
+
detail: `path '${raw}' points above the module root. A native file must ship inside the module directory.`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
out.pop();
|
|
120
|
+
}
|
|
121
|
+
if (out.length === 0) {
|
|
122
|
+
return { kind: "invalid", detail: `path '${raw}' resolves to the module root, not to a file.` };
|
|
123
|
+
}
|
|
124
|
+
return { path: out.join("/") };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Read the `native:` block off an owner document's JSON projection.
|
|
129
|
+
*
|
|
130
|
+
* An entry with any problem is left out of `entries`, never read partially: a
|
|
131
|
+
* half-read entry would name a layer the author did not declare.
|
|
132
|
+
*/
|
|
133
|
+
export function readNativeEntries(ownerJson: unknown): NativeEntries {
|
|
134
|
+
const declared = (ownerJson as { native?: unknown } | null)?.native;
|
|
135
|
+
const entries: NativeEntry[] = [];
|
|
136
|
+
const problems: NativeEntryProblem[] = [];
|
|
137
|
+
if (declared === undefined) return { entries, problems };
|
|
138
|
+
if (!Array.isArray(declared)) {
|
|
139
|
+
problems.push({
|
|
140
|
+
kind: "shape",
|
|
141
|
+
origin: "native",
|
|
142
|
+
path: "native",
|
|
143
|
+
message: "native: expected a list of entries.",
|
|
144
|
+
});
|
|
145
|
+
return { entries, problems };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
declared.forEach((raw, index) => {
|
|
149
|
+
const at = `native[${index}]`;
|
|
150
|
+
const shape = (detail: string) =>
|
|
151
|
+
problems.push({ kind: "shape", index, origin: at, path: at, message: `${at}: ${detail}` });
|
|
152
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
153
|
+
shape("expected an object.");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const entry = raw as Record<string, unknown>;
|
|
157
|
+
|
|
158
|
+
const unknown = Object.keys(entry).filter((key) => !KNOWN_KEYS.has(key));
|
|
159
|
+
if (unknown.length > 0) {
|
|
160
|
+
shape(
|
|
161
|
+
`unknown ${unknown.length === 1 ? "key" : "keys"} ${unknown.map((k) => `'${k}'`).join(", ")}. ` +
|
|
162
|
+
`Known: ${[...KNOWN_KEYS].join(", ")}.`,
|
|
163
|
+
);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const requiredKeys = ["name", "format", ...REQUIRED_AXES, "path"];
|
|
167
|
+
const missing = requiredKeys.filter((key) => typeof entry[key] !== "string");
|
|
168
|
+
if (missing.length > 0) {
|
|
169
|
+
shape(`${missing.map((k) => `'${k}'`).join(", ")} must be strings.`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// A blank string satisfies the schema's `type: string`, so it is reported here.
|
|
173
|
+
const blank = requiredKeys.filter((key) => (entry[key] as string).trim() === "");
|
|
174
|
+
if (blank.length > 0) {
|
|
175
|
+
for (const key of blank) {
|
|
176
|
+
problems.push({
|
|
177
|
+
kind: "invalid",
|
|
178
|
+
index,
|
|
179
|
+
origin: at,
|
|
180
|
+
path: `${at}.${key}`,
|
|
181
|
+
message: `${at}: '${key}' must not be empty.`,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const name = entry.name as string;
|
|
188
|
+
const origin = `${at} ('${name}')`;
|
|
189
|
+
if (!NAME_TOKEN.test(name)) {
|
|
190
|
+
problems.push({
|
|
191
|
+
kind: "invalid",
|
|
192
|
+
index,
|
|
193
|
+
origin,
|
|
194
|
+
path: `${at}.name`,
|
|
195
|
+
message:
|
|
196
|
+
`${origin}: name '${name}' is not a canonical token. Use lowercase letters, digits, ` +
|
|
197
|
+
`'.', '-' or '_', starting with a letter or digit.`,
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let selector: ArtifactSelector;
|
|
203
|
+
try {
|
|
204
|
+
selector = selectorFromQualifiers(entry.format, entry, origin);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
if (!(err instanceof ArtifactSelectorError)) throw err;
|
|
207
|
+
problems.push({ kind: "invalid", index, origin, path: at, message: err.message });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const verdict = normalizeNativePath((entry.path as string).trim());
|
|
212
|
+
if (!("path" in verdict)) {
|
|
213
|
+
problems.push({
|
|
214
|
+
kind: verdict.kind,
|
|
215
|
+
index,
|
|
216
|
+
origin,
|
|
217
|
+
path: `${at}.path`,
|
|
218
|
+
message: `${origin}: ${verdict.detail}`,
|
|
219
|
+
});
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
entries.push({ name, selector, path: verdict.path, index, origin });
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return { entries, problems };
|
|
227
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { distance } from "./levenshtein.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* THE closest candidate to a misspelled name, or undefined.
|
|
5
|
+
*
|
|
6
|
+
* One rule, because a suggestion here is emitted as a `DiagnosticFix` — a
|
|
7
|
+
* whole-value replacement an editor applies in one click — so "closest" has to
|
|
8
|
+
* mean the same thing wherever it is offered. Two properties carry that:
|
|
9
|
+
*
|
|
10
|
+
* - **A TIE returns undefined.** Two candidates at equal distance make the pick
|
|
11
|
+
* arbitrary, and an arbitrary pick that is one click from being applied is
|
|
12
|
+
* worse than no suggestion: it resolves by whatever order the candidate set
|
|
13
|
+
* happened to be built in, which is a `Set`'s insertion order.
|
|
14
|
+
* - **The threshold scales but is CAPPED** (`min(3, len/3)`, and nothing under
|
|
15
|
+
* 1). Without the cap a long name accepts a distant match; without the floor
|
|
16
|
+
* a two-character name gets a "correction" sharing nothing with it.
|
|
17
|
+
*
|
|
18
|
+
* Both are `computeSuggestKind`'s rule, lifted here so the kind-suggestion, the
|
|
19
|
+
* template-body and the export checks cannot drift into three answers — which
|
|
20
|
+
* they had, two of them byte-identical copies differing from this one in both
|
|
21
|
+
* properties.
|
|
22
|
+
*/
|
|
23
|
+
export function nearestName(
|
|
24
|
+
target: string,
|
|
25
|
+
candidates: Iterable<string>,
|
|
26
|
+
): string | undefined {
|
|
27
|
+
if (!target) return undefined;
|
|
28
|
+
const threshold = Math.min(3, Math.floor(target.length / 3));
|
|
29
|
+
if (threshold < 1) return undefined;
|
|
30
|
+
|
|
31
|
+
let best: string | undefined;
|
|
32
|
+
let bestDist = threshold + 1;
|
|
33
|
+
let tied = false;
|
|
34
|
+
|
|
35
|
+
for (const candidate of candidates) {
|
|
36
|
+
const d = distance(target, candidate);
|
|
37
|
+
if (d < bestDist) {
|
|
38
|
+
best = candidate;
|
|
39
|
+
bestDist = d;
|
|
40
|
+
tied = false;
|
|
41
|
+
} else if (d === bestDist) {
|
|
42
|
+
tied = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return !best || bestDist > threshold || tied ? undefined : best;
|
|
47
|
+
}
|