@telorun/ide-support 0.13.3 → 0.14.1
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/cel-chain.d.ts +36 -0
- package/dist/cel-chain.d.ts.map +1 -0
- package/dist/cel-chain.js +77 -0
- package/dist/completions/detect-context.d.ts +5 -5
- package/dist/completions/detect-context.d.ts.map +1 -1
- package/dist/completions/detect-context.js +72 -5
- package/dist/completions/prop-keys.d.ts +1 -1
- package/dist/completions/prop-keys.d.ts.map +1 -1
- package/dist/completions/prop-keys.js +18 -1
- package/dist/definition/resolve-cel-target.d.ts.map +1 -1
- package/dist/definition/resolve-cel-target.js +1 -60
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/rename/build-rename.d.ts +39 -0
- package/dist/rename/build-rename.d.ts.map +1 -0
- package/dist/rename/build-rename.js +407 -0
- package/dist/rename/find-sites.d.ts +49 -0
- package/dist/rename/find-sites.d.ts.map +1 -0
- package/dist/rename/find-sites.js +202 -0
- package/dist/rename/index.d.ts +3 -0
- package/dist/rename/index.d.ts.map +1 -0
- package/dist/rename/index.js +1 -0
- package/dist/rename/types.d.ts +55 -0
- package/dist/rename/types.d.ts.map +1 -0
- package/dist/rename/types.js +1 -0
- package/package.json +2 -2
- package/src/cel-chain.ts +86 -0
- package/src/completions/detect-context.ts +73 -6
- package/src/completions/prop-keys.ts +23 -2
- package/src/definition/resolve-cel-target.ts +1 -68
- package/src/index.ts +1 -0
- package/src/rename/build-rename.ts +513 -0
- package/src/rename/find-sites.ts +215 -0
- package/src/rename/index.ts +9 -0
- package/src/rename/types.ts +51 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Range } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Which naming surface a rename targets. Only the value-level, module-local
|
|
4
|
+
* ones are supported — see `build-rename.ts` for why each of the others is a
|
|
5
|
+
* refusal rather than an omission.
|
|
6
|
+
*/
|
|
7
|
+
export type RenameSymbolKind =
|
|
8
|
+
/** A resource instance's `metadata.name`. */
|
|
9
|
+
"resource"
|
|
10
|
+
/** A `name:` on a step inside a step array. */
|
|
11
|
+
| "step"
|
|
12
|
+
/** A key of the module doc's `variables:` / `secrets:` / `ports:` block. */
|
|
13
|
+
| "declaration";
|
|
14
|
+
/** What the cursor resolved to, and the span the host pre-fills in its rename
|
|
15
|
+
* box. `block` is set only for a `declaration`. */
|
|
16
|
+
export interface RenameSymbol {
|
|
17
|
+
kind: RenameSymbolKind;
|
|
18
|
+
name: string;
|
|
19
|
+
range: Range;
|
|
20
|
+
block?: "variables" | "secrets" | "ports";
|
|
21
|
+
}
|
|
22
|
+
/** One replacement. `range` is a source span in `uri`; `newText` replaces it
|
|
23
|
+
* wholesale. Spans never overlap and never cross a line, because every site is
|
|
24
|
+
* either a bare scalar value or an identifier inside one. */
|
|
25
|
+
export interface RenameEdit {
|
|
26
|
+
range: Range;
|
|
27
|
+
newText: string;
|
|
28
|
+
}
|
|
29
|
+
export interface RenameFileEdits {
|
|
30
|
+
uri: string;
|
|
31
|
+
edits: RenameEdit[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A refusal carries the reason, always. A rename the tool declines is a
|
|
35
|
+
* decision the author has to act on — silently returning "nothing to rename"
|
|
36
|
+
* would read as "this name has no references", which is the opposite of what a
|
|
37
|
+
* refusal usually means here (an exported name has too many, in files this
|
|
38
|
+
* workspace cannot see).
|
|
39
|
+
*/
|
|
40
|
+
export type RenamePreparation = {
|
|
41
|
+
ok: true;
|
|
42
|
+
symbol: RenameSymbol;
|
|
43
|
+
} | {
|
|
44
|
+
ok: false;
|
|
45
|
+
reason: string;
|
|
46
|
+
};
|
|
47
|
+
export type RenameResult = {
|
|
48
|
+
ok: true;
|
|
49
|
+
symbol: RenameSymbol;
|
|
50
|
+
files: RenameFileEdits[];
|
|
51
|
+
} | {
|
|
52
|
+
ok: false;
|
|
53
|
+
reason: string;
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rename/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;GAIG;AACH,MAAM,MAAM,gBAAgB;AAC1B,6CAA6C;AAC3C,UAAU;AACZ,+CAA+C;GAC7C,MAAM;AACR,4EAA4E;GAC1E,aAAa,CAAC;AAElB;oDACoD;AACpD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,gBAAgB,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,CAAC,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;CAC3C;AAED;;8DAE8D;AAC9D,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GAClC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,eAAe,EAAE,CAAA;CAAE,GAC5D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/ide-support",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Editor-host-agnostic IDE support (completions, diagnostic normalization) for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"src/**"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@telorun/analyzer": "0.
|
|
39
|
+
"@telorun/analyzer": "0.62.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^20.0.0",
|
package/src/cel-chain.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { CelNode } from "@telorun/analyzer";
|
|
2
|
+
|
|
3
|
+
/** One identifier of a dotted CEL chain, with the span a cursor hit-tests
|
|
4
|
+
* against and a rename replaces. */
|
|
5
|
+
export interface ChainPart {
|
|
6
|
+
name: string;
|
|
7
|
+
range: [number, number];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Flatten `a.b.c` into its identifiers. Returns undefined as soon as the chain
|
|
12
|
+
* is rooted in something other than a plain identifier (a call, an index), so a
|
|
13
|
+
* navigable — or renameable — prefix is never invented out of a computed
|
|
14
|
+
* expression.
|
|
15
|
+
*/
|
|
16
|
+
export function flattenChain(node: CelNode): ChainPart[] | undefined {
|
|
17
|
+
if (node.kind === "ident") return [{ name: node.name, range: node.range }];
|
|
18
|
+
if (node.kind !== "member") return undefined;
|
|
19
|
+
const head = flattenChain(node.target);
|
|
20
|
+
return head ? [...head, { name: node.property, range: node.propertyRange }] : undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A node's children.
|
|
25
|
+
*
|
|
26
|
+
* Exhaustive by construction: a new `CelNode` variant fails the build here
|
|
27
|
+
* rather than silently going unwalked, so the analyzer's node model and the
|
|
28
|
+
* walks over it cannot drift apart unnoticed. That matters twice over — for
|
|
29
|
+
* go-to-definition an unwalked variant is a missed jump, for a rename it is a
|
|
30
|
+
* reference left pointing at the old name.
|
|
31
|
+
*/
|
|
32
|
+
export function celChildren(node: CelNode): CelNode[] {
|
|
33
|
+
switch (node.kind) {
|
|
34
|
+
case "literal":
|
|
35
|
+
case "ident":
|
|
36
|
+
return [];
|
|
37
|
+
case "member":
|
|
38
|
+
return [node.target];
|
|
39
|
+
case "index":
|
|
40
|
+
return [node.target, node.index];
|
|
41
|
+
case "call":
|
|
42
|
+
return node.args;
|
|
43
|
+
case "methodCall":
|
|
44
|
+
return [node.receiver, ...node.args];
|
|
45
|
+
case "list":
|
|
46
|
+
return node.items;
|
|
47
|
+
case "map":
|
|
48
|
+
return node.entries.flatMap((e) => [e.key, e.value]);
|
|
49
|
+
case "ternary":
|
|
50
|
+
return [node.cond, node.then, node.else];
|
|
51
|
+
case "unary":
|
|
52
|
+
return [node.operand];
|
|
53
|
+
case "binary":
|
|
54
|
+
return [node.left, node.right];
|
|
55
|
+
}
|
|
56
|
+
const unhandled: never = node;
|
|
57
|
+
throw new Error(`Unhandled CEL node: ${JSON.stringify(unhandled)}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Every node of the tree, outermost first. */
|
|
61
|
+
export function walkCel(node: CelNode, visit: (node: CelNode) => void): void {
|
|
62
|
+
visit(node);
|
|
63
|
+
for (const child of celChildren(node)) walkCel(child, visit);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The dotted chain under `offset`, and which of its identifiers was hit. The
|
|
68
|
+
* walk is outermost-first so the longest chain wins — `resources.store.conn`
|
|
69
|
+
* resolves as one chain rather than as its `resources.store` prefix.
|
|
70
|
+
*/
|
|
71
|
+
export function chainAt(
|
|
72
|
+
node: CelNode,
|
|
73
|
+
offset: number,
|
|
74
|
+
): { parts: ChainPart[]; index: number } | undefined {
|
|
75
|
+
if (offset < node.range[0] || offset > node.range[1]) return undefined;
|
|
76
|
+
const parts = flattenChain(node);
|
|
77
|
+
if (parts) {
|
|
78
|
+
const index = parts.findIndex((p) => offset >= p.range[0] && offset <= p.range[1]);
|
|
79
|
+
if (index >= 0) return { parts, index };
|
|
80
|
+
}
|
|
81
|
+
for (const child of celChildren(node)) {
|
|
82
|
+
const hit = chainAt(child, offset);
|
|
83
|
+
if (hit) return hit;
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
@@ -66,18 +66,70 @@ function peelCombinators(node: Record<string, any>): Record<string, any>[] {
|
|
|
66
66
|
return out;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/** One JSON Pointer segment: RFC 6901 escapes, then percent-decoding, which a
|
|
70
|
+
* pointer carried in a URI fragment is subject to. A malformed escape is
|
|
71
|
+
* returned raw rather than thrown — `decodeURIComponent` raises `URIError`, and
|
|
72
|
+
* a stray `%` in someone's `$defs` key must not take completion down. */
|
|
73
|
+
function decodePointerSegment(segment: string): string {
|
|
74
|
+
const unescaped = segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
75
|
+
try {
|
|
76
|
+
return decodeURIComponent(unescaped);
|
|
77
|
+
} catch {
|
|
78
|
+
return unescaped;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Follow a document-local `$ref` (`#/$defs/<Name>`) against the schema root.
|
|
84
|
+
*
|
|
85
|
+
* The one reference form that occurs inside a kind schema, and the one the
|
|
86
|
+
* editor's resolver accepts — a schema-valued slot points at the hoisted
|
|
87
|
+
* `JsonSchema7` / `KindSchema` fragment this way, and the fragment points at
|
|
88
|
+
* itself to describe a nested schema. Without the hop, completion stopped dead
|
|
89
|
+
* at the first key of every `schema:` / `status:` block.
|
|
90
|
+
*
|
|
91
|
+
* A chain ends at a REPEATED pointer, which is what bounds the walk: a
|
|
92
|
+
* self-referential fragment is the normal case here, so a cycle must degrade to
|
|
93
|
+
* "no completion" rather than hang the editor.
|
|
94
|
+
*/
|
|
95
|
+
function resolveLocalRef(
|
|
96
|
+
node: Record<string, any> | undefined,
|
|
97
|
+
root: Record<string, any>,
|
|
98
|
+
): Record<string, any> | undefined {
|
|
99
|
+
let current = node;
|
|
100
|
+
const seen = new Set<string>();
|
|
101
|
+
while (current && typeof current.$ref === "string" && current.$ref.startsWith("#/")) {
|
|
102
|
+
const pointer = current.$ref;
|
|
103
|
+
if (seen.has(pointer)) return undefined;
|
|
104
|
+
seen.add(pointer);
|
|
105
|
+
let target: any = root;
|
|
106
|
+
for (const segment of pointer.slice(2).split("/")) {
|
|
107
|
+
target = target?.[decodePointerSegment(segment)];
|
|
108
|
+
}
|
|
109
|
+
if (!target || typeof target !== "object") return undefined;
|
|
110
|
+
// Keep whatever the slot declared beside the `$ref` (its title, its
|
|
111
|
+
// `x-telo-fragment` stamp) — that is what tells a consumer WHICH shape it
|
|
112
|
+
// pointed at, and draft-07 drops it at the validation layer only.
|
|
113
|
+
const { $ref: _, ...siblings } = current;
|
|
114
|
+
current = { ...target, ...siblings };
|
|
115
|
+
}
|
|
116
|
+
return current;
|
|
117
|
+
}
|
|
118
|
+
|
|
69
119
|
/** Navigate a JSON Schema hierarchy following `path`, auto-descending into
|
|
70
|
-
* array items
|
|
71
|
-
* branches define `properties`, returns a
|
|
72
|
-
* is the union (first-wins on key collision)
|
|
73
|
-
* intersection — enough for propKeyCompletions to
|
|
74
|
-
* at this slot can legally carry. */
|
|
120
|
+
* array items, peeling `anyOf` / `oneOf` branches and following document-local
|
|
121
|
+
* `$ref`s. When multiple peeled branches define `properties`, returns a
|
|
122
|
+
* synthetic node whose `properties` is the union (first-wins on key collision)
|
|
123
|
+
* and whose `required` is the intersection — enough for propKeyCompletions to
|
|
124
|
+
* surface every key a value at this slot can legally carry. */
|
|
75
125
|
export function navigateSchema(
|
|
76
126
|
schema: Record<string, any>,
|
|
77
127
|
path: string[],
|
|
78
128
|
): Record<string, any> | undefined {
|
|
79
|
-
let current: Record<string, any> = schema;
|
|
129
|
+
let current: Record<string, any> | undefined = schema;
|
|
80
130
|
for (const segment of path) {
|
|
131
|
+
current = resolveLocalRef(current, schema);
|
|
132
|
+
if (!current) return undefined;
|
|
81
133
|
const candidates = peelCombinators(current).flatMap((node) => {
|
|
82
134
|
const expanded: Record<string, any>[] = [];
|
|
83
135
|
let cur: Record<string, any> = node;
|
|
@@ -93,9 +145,24 @@ export function navigateSchema(
|
|
|
93
145
|
break;
|
|
94
146
|
}
|
|
95
147
|
}
|
|
148
|
+
// A map-valued node (a schema's `properties:`, a kind's name-keyed field)
|
|
149
|
+
// names its entries nowhere, so every segment lands on
|
|
150
|
+
// `additionalProperties`. Without this the walk stopped one level into a
|
|
151
|
+
// `schema:` block — at exactly the field the author is writing.
|
|
152
|
+
if (!next) {
|
|
153
|
+
for (const cand of candidates) {
|
|
154
|
+
const additional = cand.additionalProperties;
|
|
155
|
+
if (additional && typeof additional === "object") {
|
|
156
|
+
next = additional as Record<string, any>;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
96
161
|
if (!next) return undefined;
|
|
97
162
|
current = next;
|
|
98
163
|
}
|
|
164
|
+
current = resolveLocalRef(current, schema);
|
|
165
|
+
if (!current) return undefined;
|
|
99
166
|
// Auto-descend through a trailing array at the leaf (e.g. cursor inside `mounts:` items)
|
|
100
167
|
while (current.type === "array" && current.items) {
|
|
101
168
|
current = current.items as Record<string, any>;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
manifestFragmentOf,
|
|
3
|
+
TELO_SCHEMA_ANNOTATIONS,
|
|
4
|
+
type AnalysisRegistry,
|
|
5
|
+
} from "@telorun/analyzer";
|
|
2
6
|
import type { CompletionResult } from "../types.js";
|
|
3
7
|
import { navigateSchema } from "./detect-context.js";
|
|
4
8
|
|
|
@@ -52,11 +56,28 @@ export function propKeyCompletions(
|
|
|
52
56
|
const properties =
|
|
53
57
|
yamlPath.length === 0
|
|
54
58
|
? { ...ROOT_IMPLICIT_PROPS, ...(targetSchema.properties as Record<string, any>) }
|
|
55
|
-
: (targetSchema.properties as Record<string, any>);
|
|
59
|
+
: { ...(targetSchema.properties as Record<string, any>), ...annotationKeys(targetSchema) };
|
|
56
60
|
|
|
57
61
|
return buildItems(properties, existingKeys, required);
|
|
58
62
|
}
|
|
59
63
|
|
|
64
|
+
/**
|
|
65
|
+
* The `x-telo-*` vocabulary, offered only inside a KIND's own schema.
|
|
66
|
+
*
|
|
67
|
+
* Read off the fragment stamp rather than the position: a kind schema nests, so
|
|
68
|
+
* "am I inside one" is a fact about the node, not about how deep the cursor
|
|
69
|
+
* sits. A plain data schema — an `inputType:`, a `status:` block — stamps
|
|
70
|
+
* `JsonSchema7` and gets nothing, because an annotation there configures a slot
|
|
71
|
+
* that does not exist.
|
|
72
|
+
*
|
|
73
|
+
* The vocabulary lives on the analyzer side and never enters a manifest: a
|
|
74
|
+
* `properties` map holding a literal `x-telo-ref` key reads to the annotation
|
|
75
|
+
* walkers as an annotated node.
|
|
76
|
+
*/
|
|
77
|
+
function annotationKeys(node: Record<string, any>): Record<string, any> {
|
|
78
|
+
return manifestFragmentOf(node) === "KindSchema" ? TELO_SCHEMA_ANNOTATIONS : {};
|
|
79
|
+
}
|
|
80
|
+
|
|
60
81
|
function buildItems(
|
|
61
82
|
properties: Record<string, any>,
|
|
62
83
|
existingKeys: Set<string>,
|
|
@@ -13,79 +13,12 @@ import {
|
|
|
13
13
|
moduleFiles,
|
|
14
14
|
} from "./manifest-navigation.js";
|
|
15
15
|
import { resolveExportedResource } from "./resolve-export-chain.js";
|
|
16
|
+
import { chainAt, type ChainPart } from "../cel-chain.js";
|
|
16
17
|
|
|
17
18
|
/** Root CEL scopes whose members are declared as a block on the module doc, so
|
|
18
19
|
* `variables.port` navigates to `variables:` and then to its `port:` entry. */
|
|
19
20
|
const DECLARATION_SCOPES = new Set(["variables", "secrets", "ports"]);
|
|
20
21
|
|
|
21
|
-
/** One identifier of a dotted CEL chain, with the span the cursor hit-tests
|
|
22
|
-
* against. */
|
|
23
|
-
interface ChainPart {
|
|
24
|
-
name: string;
|
|
25
|
-
range: [number, number];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Flatten `a.b.c` into its identifiers. Returns undefined as soon as the chain
|
|
29
|
-
* is rooted in something other than a plain identifier (a call, an index), so a
|
|
30
|
-
* navigable prefix is never invented out of a computed expression. */
|
|
31
|
-
function flattenChain(node: CelNode): ChainPart[] | undefined {
|
|
32
|
-
if (node.kind === "ident") return [{ name: node.name, range: node.range }];
|
|
33
|
-
if (node.kind !== "member") return undefined;
|
|
34
|
-
const head = flattenChain(node.target);
|
|
35
|
-
return head ? [...head, { name: node.property, range: node.propertyRange }] : undefined;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Exhaustive by construction: a new `CelNode` variant fails the build here
|
|
39
|
-
* rather than silently going unwalked, so the analyzer's node model and this
|
|
40
|
-
* walk cannot drift apart unnoticed. */
|
|
41
|
-
function celChildren(node: CelNode): CelNode[] {
|
|
42
|
-
switch (node.kind) {
|
|
43
|
-
case "literal":
|
|
44
|
-
case "ident":
|
|
45
|
-
return [];
|
|
46
|
-
case "member":
|
|
47
|
-
return [node.target];
|
|
48
|
-
case "index":
|
|
49
|
-
return [node.target, node.index];
|
|
50
|
-
case "call":
|
|
51
|
-
return node.args;
|
|
52
|
-
case "methodCall":
|
|
53
|
-
return [node.receiver, ...node.args];
|
|
54
|
-
case "list":
|
|
55
|
-
return node.items;
|
|
56
|
-
case "map":
|
|
57
|
-
return node.entries.flatMap((e) => [e.key, e.value]);
|
|
58
|
-
case "ternary":
|
|
59
|
-
return [node.cond, node.then, node.else];
|
|
60
|
-
case "unary":
|
|
61
|
-
return [node.operand];
|
|
62
|
-
case "binary":
|
|
63
|
-
return [node.left, node.right];
|
|
64
|
-
}
|
|
65
|
-
const unhandled: never = node;
|
|
66
|
-
throw new Error(`Unhandled CEL node: ${JSON.stringify(unhandled)}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** The dotted chain under `offset`, and which of its identifiers was hit. The
|
|
70
|
-
* walk is outermost-first so the longest chain wins — `resources.Store.conn`
|
|
71
|
-
* resolves as one chain rather than as its `resources.Store` prefix. */
|
|
72
|
-
function chainAt(
|
|
73
|
-
node: CelNode,
|
|
74
|
-
offset: number,
|
|
75
|
-
): { parts: ChainPart[]; index: number } | undefined {
|
|
76
|
-
if (offset < node.range[0] || offset > node.range[1]) return undefined;
|
|
77
|
-
const parts = flattenChain(node);
|
|
78
|
-
if (parts) {
|
|
79
|
-
const index = parts.findIndex((p) => offset >= p.range[0] && offset <= p.range[1]);
|
|
80
|
-
if (index >= 0) return { parts, index };
|
|
81
|
-
}
|
|
82
|
-
for (const child of celChildren(node)) {
|
|
83
|
-
const hit = chainAt(child, offset);
|
|
84
|
-
if (hit) return hit;
|
|
85
|
-
}
|
|
86
|
-
return undefined;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
22
|
/** `resources.<name>` is a local instance; `resources.<Alias>.<name>` is an
|
|
90
23
|
* imported module's exported one. A local name wins over an import alias, so a
|
|
91
24
|
* deeper access on a local instance (`resources.db.url`) reads as a field of
|
package/src/index.ts
CHANGED