@idfkit/language 0.0.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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/complete.d.ts +93 -0
- package/dist/complete.d.ts.map +1 -0
- package/dist/complete.js +225 -0
- package/dist/complete.js.map +1 -0
- package/dist/cursor.d.ts +59 -0
- package/dist/cursor.d.ts.map +1 -0
- package/dist/cursor.js +286 -0
- package/dist/cursor.js.map +1 -0
- package/dist/declaration.d.ts +70 -0
- package/dist/declaration.d.ts.map +1 -0
- package/dist/declaration.js +184 -0
- package/dist/declaration.js.map +1 -0
- package/dist/explain.d.ts +97 -0
- package/dist/explain.d.ts.map +1 -0
- package/dist/explain.js +130 -0
- package/dist/explain.js.map +1 -0
- package/dist/findings.d.ts +58 -0
- package/dist/findings.d.ts.map +1 -0
- package/dist/findings.js +325 -0
- package/dist/findings.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/dist/cursor.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the cursor is, found by scanning outwards from it.
|
|
3
|
+
*
|
|
4
|
+
* **This never builds or consults a `SyntaxLayer`, and it must not start.** Building a layer to
|
|
5
|
+
* answer a cursor is the reparse the whole design exists to avoid: it costs the file, on a
|
|
6
|
+
* keystroke, for an answer that concerns one statement. The committed measurement asks the same
|
|
7
|
+
* question of a large model and of a file a hundredth its size and requires the two to come out
|
|
8
|
+
* within a small factor, which is a check a reparse cannot pass on any machine.
|
|
9
|
+
*
|
|
10
|
+
* The method is a backward scan to the nearest semicolon that is not inside a comment, then a
|
|
11
|
+
* forward scan over that one statement. Both halves are sound only because of what this grammar
|
|
12
|
+
* lacks: IDF has no nesting, no string literals and no escape sequences, so a semicolon terminates
|
|
13
|
+
* a statement unconditionally unless a comment swallowed it, and a comment is an exclamation mark
|
|
14
|
+
* running to the end of its line. Deciding whether a candidate semicolon is real therefore costs
|
|
15
|
+
* the length of its line, and finding the statement costs the distance back to the previous real
|
|
16
|
+
* terminator. Neither number grows with the file. Almost no other grammar would permit this, and
|
|
17
|
+
* a language that gained a string literal would break it silently, so the property is stated here
|
|
18
|
+
* rather than left for a reader to rediscover.
|
|
19
|
+
*
|
|
20
|
+
* The honest bound is the distance back to the previous real terminator rather than the length of
|
|
21
|
+
* the statement. Those are the same number in ordinary files and differ in one real case: a cursor
|
|
22
|
+
* in the first statement of a file that opens with a large comment header scans back through the
|
|
23
|
+
* header. Headers are small, and the case is still linear with a tiny constant.
|
|
24
|
+
*
|
|
25
|
+
* Nothing throws, for any input. An offset outside `[0, text.length]` is clamped into range rather
|
|
26
|
+
* than refused (FR-032): a cursor arrives from an editor that may be a keystroke ahead of the text
|
|
27
|
+
* it was measured against, and answering about the nearest character beats answering nothing.
|
|
28
|
+
*
|
|
29
|
+
* The schema is optional. Without one the context still reports the statement, the part, and the
|
|
30
|
+
* field index, which is what positions a finding about a type no schema defines.
|
|
31
|
+
*/
|
|
32
|
+
export function contextAt(text, offset, schema) {
|
|
33
|
+
const at = clampOffset(offset, text.length);
|
|
34
|
+
const scanned = scanStatement(text, statementScanStart(text, at), at);
|
|
35
|
+
const statement = scanned.statement ?? emptyStatementAt(at);
|
|
36
|
+
const part = partOf(scanned);
|
|
37
|
+
const fieldIndex = part === 'field' ? scanned.slot - 1 : undefined;
|
|
38
|
+
const typeName = schema?.resolve(statement.typeNameText);
|
|
39
|
+
const type = typeName === undefined ? undefined : schema?.get(typeName);
|
|
40
|
+
const fieldName = type === undefined || fieldIndex === undefined ? undefined : fieldNameAt(type, fieldIndex);
|
|
41
|
+
return { statement, at: part, fieldIndex, typeName, fieldName };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The schema field name at a positional index, or `undefined` past the end of the type.
|
|
45
|
+
*
|
|
46
|
+
* Positional order is the schema's own: the fixed fields in `f`, whose index 0 is the name on a
|
|
47
|
+
* named type, and then the extensible group repeating its `fields` for as long as values were
|
|
48
|
+
* written. This mirrors how `parseIdf` maps positional values onto named fields, deliberately and
|
|
49
|
+
* exactly, because a cursor that disagreed with the reader about which field it was in would offer
|
|
50
|
+
* the neighbouring field's values.
|
|
51
|
+
*
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
export function fieldNameAt(type, index) {
|
|
55
|
+
if (index < 0)
|
|
56
|
+
return undefined;
|
|
57
|
+
const fixed = type.f;
|
|
58
|
+
if (index < fixed.length)
|
|
59
|
+
return fixed[index];
|
|
60
|
+
const extensible = type.x;
|
|
61
|
+
const width = extensible?.fields.length ?? 0;
|
|
62
|
+
if (extensible === undefined || width === 0)
|
|
63
|
+
return undefined;
|
|
64
|
+
return extensible.fields[(index - fixed.length) % width];
|
|
65
|
+
}
|
|
66
|
+
/** Which part of the statement the scan put the offset on. */
|
|
67
|
+
function partOf(scanned) {
|
|
68
|
+
// A comment wins over everything, including the field it interrupts: nothing completes and
|
|
69
|
+
// nothing explains inside one, whether it sits between two statements or in the middle of a
|
|
70
|
+
// field's value.
|
|
71
|
+
if (scanned.inComment)
|
|
72
|
+
return 'comment';
|
|
73
|
+
// No statement had opened by the time the scan passed the offset, so the offset is in the
|
|
74
|
+
// whitespace after a terminator and before whatever comes next, which includes trailing
|
|
75
|
+
// whitespace at end of file. That is the state in which a new statement is beginning.
|
|
76
|
+
if (scanned.statement === undefined)
|
|
77
|
+
return 'betweenStatements';
|
|
78
|
+
return scanned.slot === 0 ? 'typeName' : 'field';
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The statement a cursor is beginning to write, standing in for one that is not there yet.
|
|
82
|
+
*
|
|
83
|
+
* `CursorContext.statement` is not optional, and making it optional to describe an empty line
|
|
84
|
+
* would push a branch onto every consumer to serve the one state in which there is nothing to
|
|
85
|
+
* describe. An empty statement at the cursor is the truthful alternative: it selects nothing,
|
|
86
|
+
* which is exactly the region an offer inserted here would replace, and it reports
|
|
87
|
+
* `unterminated`, because a statement nobody has written carries no terminator.
|
|
88
|
+
*/
|
|
89
|
+
function emptyStatementAt(offset) {
|
|
90
|
+
const region = { start: offset, end: offset };
|
|
91
|
+
return { region, typeName: region, typeNameText: '', fields: [], unterminated: true };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Offset to begin the forward scan at: one past the nearest real terminator before `offset`.
|
|
95
|
+
*
|
|
96
|
+
* Backwards through the semicolons with `lastIndexOf`, which is a native scan rather than a
|
|
97
|
+
* character loop, testing each candidate for comment membership and skipping the ones a comment
|
|
98
|
+
* swallowed. A file with no terminator before the offset starts at zero, which is the only other
|
|
99
|
+
* place a statement can begin.
|
|
100
|
+
*/
|
|
101
|
+
function statementScanStart(text, offset) {
|
|
102
|
+
for (let at = offset - 1; at >= 0; at -= 1) {
|
|
103
|
+
const found = text.lastIndexOf(';', at);
|
|
104
|
+
if (found < 0)
|
|
105
|
+
return 0;
|
|
106
|
+
if (!insideComment(text, found))
|
|
107
|
+
return found + 1;
|
|
108
|
+
// Step past the one a comment swallowed and keep looking. `at -= 1` runs next, so a candidate
|
|
109
|
+
// at offset zero leaves the loop rather than finding itself again.
|
|
110
|
+
at = found;
|
|
111
|
+
}
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Whether the character at `index` sits inside a comment.
|
|
116
|
+
*
|
|
117
|
+
* Back to the start of its line, then forward for an exclamation mark before it. There are no
|
|
118
|
+
* string literals and no escapes, so an exclamation mark on the line before this character always
|
|
119
|
+
* opened a comment that is still open here, and one line is all it costs to know.
|
|
120
|
+
*
|
|
121
|
+
* The forward search is what keeps that promise. `lastIndexOf('!', index)` answers the same
|
|
122
|
+
* question and reads the whole prefix to do it whenever the file holds no exclamation mark before
|
|
123
|
+
* `index`, which is every position in a machine-written file that carries no comments at all: the
|
|
124
|
+
* cost then grows with how far into the file the cursor is, which is precisely what this module
|
|
125
|
+
* exists not to do. Searching forward from the line's own start stops at the line's end instead.
|
|
126
|
+
*/
|
|
127
|
+
function insideComment(text, index) {
|
|
128
|
+
const lineStart = index === 0 ? 0 : text.lastIndexOf('\n', index - 1) + 1;
|
|
129
|
+
const opened = text.indexOf('!', lineStart);
|
|
130
|
+
return opened !== -1 && opened <= index;
|
|
131
|
+
}
|
|
132
|
+
const EXCLAMATION = 0x21;
|
|
133
|
+
const COMMA = 0x2c;
|
|
134
|
+
const SEMICOLON = 0x3b;
|
|
135
|
+
/**
|
|
136
|
+
* Read one statement forward from `from`, stopping as soon as the offset is placed.
|
|
137
|
+
*
|
|
138
|
+
* A second implementation of the character rules `@idfkit/core`'s internal scan already holds, and
|
|
139
|
+
* that is a deliberate cost rather than an oversight. The scan is internal to core on purpose, so
|
|
140
|
+
* that the syntax layer and the model-building read cannot drift apart; reaching it from here
|
|
141
|
+
* would mean publishing it, which is the hazard that decision exists to prevent. The rules
|
|
142
|
+
* reproduced here are the four that matter, and the field bounds are computed exactly as the layer
|
|
143
|
+
* computes them, so that a region reported by a cursor and a region reported by a finding select
|
|
144
|
+
* the same characters.
|
|
145
|
+
*
|
|
146
|
+
* The pass stops at the statement's terminator, or at end of input, or as soon as it is past the
|
|
147
|
+
* offset with nothing open, which is what keeps the cost the statement's rather than the file's.
|
|
148
|
+
*/
|
|
149
|
+
function scanStatement(text, from, offset) {
|
|
150
|
+
const length = text.length;
|
|
151
|
+
/** 0 for the type name, 1 for the first field after it. */
|
|
152
|
+
let fieldIndex = 0;
|
|
153
|
+
/** True once the statement's first non-blank, non-comment character has been seen. */
|
|
154
|
+
let open = false;
|
|
155
|
+
/** Where that character was. */
|
|
156
|
+
let openedAt = 0;
|
|
157
|
+
/** First non-blank character of the current field's value, or -1 while it has none. */
|
|
158
|
+
let valueStart = -1;
|
|
159
|
+
/** One past the last non-blank character of the run `valueStart` falls in. */
|
|
160
|
+
let valueEnd = -1;
|
|
161
|
+
/** True once a comment has ended the run holding `valueStart`. */
|
|
162
|
+
let valueClosed = false;
|
|
163
|
+
let typeName = { start: from, end: from };
|
|
164
|
+
const fields = [];
|
|
165
|
+
let inComment = false;
|
|
166
|
+
let slot = 0;
|
|
167
|
+
let statement;
|
|
168
|
+
/**
|
|
169
|
+
* Record the field the scan is inside, closed at `at`.
|
|
170
|
+
*
|
|
171
|
+
* A field with no value text gets an empty region at `at`, the separator or terminator or end of
|
|
172
|
+
* input that closed it, because everything before it was whitespace or comment. That is what
|
|
173
|
+
* keeps positional indexing sound through a blank slot in the middle of an extensible group.
|
|
174
|
+
*
|
|
175
|
+
* A field written on both sides of a comment that interrupts it keeps the first run alone, which
|
|
176
|
+
* is what the layer stores, so a value region and a comment region can never overlap.
|
|
177
|
+
*/
|
|
178
|
+
const closeField = (at) => {
|
|
179
|
+
const region = valueStart < 0 ? { start: at, end: at } : { start: valueStart, end: valueEnd };
|
|
180
|
+
if (fieldIndex === 0)
|
|
181
|
+
typeName = region;
|
|
182
|
+
else
|
|
183
|
+
fields.push(region);
|
|
184
|
+
valueStart = -1;
|
|
185
|
+
valueEnd = -1;
|
|
186
|
+
valueClosed = false;
|
|
187
|
+
};
|
|
188
|
+
const finish = (end, unterminated) => ({
|
|
189
|
+
region: { start: openedAt, end },
|
|
190
|
+
typeName,
|
|
191
|
+
typeNameText: text.slice(typeName.start, typeName.end),
|
|
192
|
+
fields,
|
|
193
|
+
unterminated,
|
|
194
|
+
});
|
|
195
|
+
let index = from;
|
|
196
|
+
while (index < length) {
|
|
197
|
+
// Nothing has opened and the offset is behind us, so no statement contains it and none that
|
|
198
|
+
// opens later can. Every comment that could hold the offset began at or before it and has
|
|
199
|
+
// already been tested.
|
|
200
|
+
if (!open && index > offset)
|
|
201
|
+
break;
|
|
202
|
+
const code = text.charCodeAt(index);
|
|
203
|
+
if (code === EXCLAMATION) {
|
|
204
|
+
const feed = text.indexOf('\n', index);
|
|
205
|
+
const end = feed === -1 ? length : feed;
|
|
206
|
+
// Inclusive at both ends. A cursor on the exclamation mark is in the comment it opens, and a
|
|
207
|
+
// cursor one past its last character is at the end of the comment's text rather than in the
|
|
208
|
+
// whitespace of the next line: typing there extends the comment.
|
|
209
|
+
if (offset >= index && offset <= end)
|
|
210
|
+
inComment = true;
|
|
211
|
+
// The comment ends the run holding the value but not the field, so `Zone1, !- Name` leaves
|
|
212
|
+
// the value at `Zone1` and text after the line feed still belongs to the same field.
|
|
213
|
+
if (valueStart >= 0)
|
|
214
|
+
valueClosed = true;
|
|
215
|
+
if (feed === -1)
|
|
216
|
+
break;
|
|
217
|
+
index = feed + 1;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const blank = isSpace(code);
|
|
221
|
+
if (!open && !blank) {
|
|
222
|
+
open = true;
|
|
223
|
+
openedAt = index;
|
|
224
|
+
}
|
|
225
|
+
if (code === COMMA || code === SEMICOLON) {
|
|
226
|
+
closeField(index);
|
|
227
|
+
if (code === SEMICOLON) {
|
|
228
|
+
statement = finish(index + 1, false);
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
if (index < offset)
|
|
232
|
+
slot += 1;
|
|
233
|
+
fieldIndex += 1;
|
|
234
|
+
index += 1;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (!blank) {
|
|
238
|
+
if (valueStart < 0) {
|
|
239
|
+
valueStart = index;
|
|
240
|
+
valueEnd = index + 1;
|
|
241
|
+
}
|
|
242
|
+
else if (!valueClosed) {
|
|
243
|
+
valueEnd = index + 1;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
index += 1;
|
|
247
|
+
}
|
|
248
|
+
// Input ran out inside a statement. Its last field is closed like any other, so the cursor reads
|
|
249
|
+
// what was written rather than reconstructing it from the leftovers, and the statement says it
|
|
250
|
+
// runs to the end.
|
|
251
|
+
if (statement === undefined && open) {
|
|
252
|
+
closeField(length);
|
|
253
|
+
statement = finish(length, true);
|
|
254
|
+
}
|
|
255
|
+
return { statement, inComment, slot };
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Whether a character is whitespace, by the definition `String.prototype.trim` uses.
|
|
259
|
+
*
|
|
260
|
+
* The same set the core scan tests, character for character. A field's value is trimmed, so a
|
|
261
|
+
* disagreement about one character would put a region beside a value rather than on it, and the
|
|
262
|
+
* cursor and the syntax layer would then report different bounds for the same field.
|
|
263
|
+
*/
|
|
264
|
+
function isSpace(code) {
|
|
265
|
+
if (code < 0x80)
|
|
266
|
+
return code === 0x20 || (code >= 0x09 && code <= 0x0d);
|
|
267
|
+
return (code === 0xa0 ||
|
|
268
|
+
code === 0x1680 ||
|
|
269
|
+
(code >= 0x2000 && code <= 0x200a) ||
|
|
270
|
+
code === 0x2028 ||
|
|
271
|
+
code === 0x2029 ||
|
|
272
|
+
code === 0x202f ||
|
|
273
|
+
code === 0x205f ||
|
|
274
|
+
code === 0x3000 ||
|
|
275
|
+
code === 0xfeff);
|
|
276
|
+
}
|
|
277
|
+
/** Into `[0, max]`, whole. `NaN` lands at 0, since no position is nearer than another. */
|
|
278
|
+
function clampOffset(value, max) {
|
|
279
|
+
if (Number.isNaN(value))
|
|
280
|
+
return 0;
|
|
281
|
+
const whole = Math.trunc(value);
|
|
282
|
+
if (whole < 0)
|
|
283
|
+
return 0;
|
|
284
|
+
return whole > max ? max : whole;
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../src/cursor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type IdfDocument, type Region, type Schema } from '@idfkit/core';
|
|
2
|
+
/** Where one object declares the name the cursor is on. */
|
|
3
|
+
export interface Declaration {
|
|
4
|
+
/**
|
|
5
|
+
* The region where the name is declared, meaning the name field of the declaring statement.
|
|
6
|
+
*
|
|
7
|
+
* A region in the text this was asked about, not in the document: the document holds no
|
|
8
|
+
* positions, and it is the text the reader is looking at. It identifies a thing rather than
|
|
9
|
+
* something to draw, so it stays whole and may cross a line boundary (FR-050).
|
|
10
|
+
*/
|
|
11
|
+
readonly region: Region;
|
|
12
|
+
/** The declaring statement's canonical type. */
|
|
13
|
+
readonly typeName: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* What the value under the cursor names, or why it names nothing.
|
|
17
|
+
*
|
|
18
|
+
* The same discriminated shape as `CompletionResult`, for the same reason: "this value names
|
|
19
|
+
* nothing that exists" and "I could not consult a schema" are different states, and a consumer
|
|
20
|
+
* that wants to tell a reader why nothing happened has the reason (FR-031).
|
|
21
|
+
*
|
|
22
|
+
* `'ok'` with no declarations is a legitimate state and a different one from all three below: it
|
|
23
|
+
* says the field points into a reference list and nothing in the document declares this name.
|
|
24
|
+
* Where the name is declared nowhere, none is reported and none is guessed at; the
|
|
25
|
+
* dangling-reference finding is what tells the reader why.
|
|
26
|
+
*/
|
|
27
|
+
export type DeclarationResult = {
|
|
28
|
+
readonly status: 'ok';
|
|
29
|
+
readonly declarations: readonly Declaration[];
|
|
30
|
+
} | {
|
|
31
|
+
readonly status: 'noSchema';
|
|
32
|
+
} | {
|
|
33
|
+
readonly status: 'unknownType';
|
|
34
|
+
readonly typeName: string;
|
|
35
|
+
} | {
|
|
36
|
+
readonly status: 'notApplicable';
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Where the name under the cursor is declared.
|
|
40
|
+
*
|
|
41
|
+
* Two steps, and keeping them apart is what keeps this honest. Which objects declare the name is
|
|
42
|
+
* resolved against the caller's document, by the two rules the model itself uses and no third one
|
|
43
|
+
* (FR-029): the object's own name, when its type's `nref` contributes to a list this field points
|
|
44
|
+
* into, and an ordinary field's value, when that field's `ref` does. Only then is the declaring
|
|
45
|
+
* statement located in the text, by looking for the name the document holds and confirming with a
|
|
46
|
+
* cursor context that the match really is that type's declaring field. So the text is never
|
|
47
|
+
* searched for a string that happens to match: it is searched for a name the document has already
|
|
48
|
+
* said is declared, and a match that turns out to be a value somewhere else is discarded.
|
|
49
|
+
*
|
|
50
|
+
* The document is the caller's, exactly as it is for reference completion, and for the same reason
|
|
51
|
+
* (research R9): declarations are a whole-document fact, every consumer already holds a document,
|
|
52
|
+
* and parsing one here would put the eighty milliseconds this design exists to avoid back on the
|
|
53
|
+
* keystroke path. A document a keystroke behind the text is the correct input rather than a stale
|
|
54
|
+
* one.
|
|
55
|
+
*
|
|
56
|
+
* **Honest bound.** Locating a declaration reads the text rather than one statement of it, because
|
|
57
|
+
* "every declaration" is a question about the whole file and cannot be answered from a bounded
|
|
58
|
+
* local scan. The reading is `String.prototype.indexOf`, a native scan of a few hundred kilobytes
|
|
59
|
+
* costing a small fraction of a millisecond, plus one bounded cursor context per candidate match,
|
|
60
|
+
* and no layer is built and no document is parsed. It is stated here rather than left for a reader
|
|
61
|
+
* to discover in a profile.
|
|
62
|
+
*
|
|
63
|
+
* `schema` is written as possibly absent rather than required, because `'noSchema'` is a state
|
|
64
|
+
* FR-031 requires this to report and a signature that forbade the input would make it unreachable
|
|
65
|
+
* from typed code.
|
|
66
|
+
*
|
|
67
|
+
* Nothing throws, for any input.
|
|
68
|
+
*/
|
|
69
|
+
export declare function declarationAt(text: string, offset: number, schema: Schema | undefined, document: IdfDocument): DeclarationResult;
|
|
70
|
+
//# sourceMappingURL=declaration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declaration.d.ts","sourceRoot":"","sources":["../src/declaration.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,KAAK,MAAM,EAEZ,MAAM,cAAc,CAAC;AAItB,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,SAAS,WAAW,EAAE,CAAA;CAAE,GACxE;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAC/B;IAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC7D;IAAE,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;CAAE,CAAC;AAMzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,EAAE,WAAW,GACpB,iBAAiB,CA2CnB"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { describeObjectType, } from '@idfkit/core';
|
|
2
|
+
import { contextAt } from './cursor.js';
|
|
3
|
+
const NOT_APPLICABLE = { status: 'notApplicable' };
|
|
4
|
+
const NO_SCHEMA = { status: 'noSchema' };
|
|
5
|
+
const NONE = { status: 'ok', declarations: [] };
|
|
6
|
+
/**
|
|
7
|
+
* Where the name under the cursor is declared.
|
|
8
|
+
*
|
|
9
|
+
* Two steps, and keeping them apart is what keeps this honest. Which objects declare the name is
|
|
10
|
+
* resolved against the caller's document, by the two rules the model itself uses and no third one
|
|
11
|
+
* (FR-029): the object's own name, when its type's `nref` contributes to a list this field points
|
|
12
|
+
* into, and an ordinary field's value, when that field's `ref` does. Only then is the declaring
|
|
13
|
+
* statement located in the text, by looking for the name the document holds and confirming with a
|
|
14
|
+
* cursor context that the match really is that type's declaring field. So the text is never
|
|
15
|
+
* searched for a string that happens to match: it is searched for a name the document has already
|
|
16
|
+
* said is declared, and a match that turns out to be a value somewhere else is discarded.
|
|
17
|
+
*
|
|
18
|
+
* The document is the caller's, exactly as it is for reference completion, and for the same reason
|
|
19
|
+
* (research R9): declarations are a whole-document fact, every consumer already holds a document,
|
|
20
|
+
* and parsing one here would put the eighty milliseconds this design exists to avoid back on the
|
|
21
|
+
* keystroke path. A document a keystroke behind the text is the correct input rather than a stale
|
|
22
|
+
* one.
|
|
23
|
+
*
|
|
24
|
+
* **Honest bound.** Locating a declaration reads the text rather than one statement of it, because
|
|
25
|
+
* "every declaration" is a question about the whole file and cannot be answered from a bounded
|
|
26
|
+
* local scan. The reading is `String.prototype.indexOf`, a native scan of a few hundred kilobytes
|
|
27
|
+
* costing a small fraction of a millisecond, plus one bounded cursor context per candidate match,
|
|
28
|
+
* and no layer is built and no document is parsed. It is stated here rather than left for a reader
|
|
29
|
+
* to discover in a profile.
|
|
30
|
+
*
|
|
31
|
+
* `schema` is written as possibly absent rather than required, because `'noSchema'` is a state
|
|
32
|
+
* FR-031 requires this to report and a signature that forbade the input would make it unreachable
|
|
33
|
+
* from typed code.
|
|
34
|
+
*
|
|
35
|
+
* Nothing throws, for any input.
|
|
36
|
+
*/
|
|
37
|
+
export function declarationAt(text, offset, schema, document) {
|
|
38
|
+
const context = contextAt(text, offset, schema);
|
|
39
|
+
// A type name declares nothing and names nothing, a comment holds no values, and between two
|
|
40
|
+
// statements there is no value to follow. Reporting `'noSchema'` in a comment would send a
|
|
41
|
+
// caller off to load one that would change this answer not at all.
|
|
42
|
+
if (context.at !== 'field')
|
|
43
|
+
return NOT_APPLICABLE;
|
|
44
|
+
if (schema === undefined)
|
|
45
|
+
return NO_SCHEMA;
|
|
46
|
+
const typeName = context.typeName;
|
|
47
|
+
if (typeName === undefined) {
|
|
48
|
+
return { status: 'unknownType', typeName: context.statement.typeNameText };
|
|
49
|
+
}
|
|
50
|
+
const type = schema.get(typeName);
|
|
51
|
+
// Unreachable through `resolve`, which only returns a name the schema holds, and kept because
|
|
52
|
+
// `describeObjectType` throws on a type the schema lacks and nothing here may throw (FR-030).
|
|
53
|
+
if (type === undefined)
|
|
54
|
+
return { status: 'unknownType', typeName };
|
|
55
|
+
const index = context.fieldIndex;
|
|
56
|
+
const fieldName = context.fieldName;
|
|
57
|
+
// No field index on a `'field'` context is unreachable, and a field the type does not define is
|
|
58
|
+
// ordinary: a statement may carry more fields than its type has.
|
|
59
|
+
if (index === undefined || fieldName === undefined)
|
|
60
|
+
return NOT_APPLICABLE;
|
|
61
|
+
const lists = listsPointedInto(schema, typeName, type, fieldName);
|
|
62
|
+
// The field points at nothing. Searching the document for an object that happens to be called
|
|
63
|
+
// whatever is written here would find one often enough to be believed and would be a guess.
|
|
64
|
+
if (lists.length === 0)
|
|
65
|
+
return NOT_APPLICABLE;
|
|
66
|
+
const written = context.statement.fields[index];
|
|
67
|
+
const target = written === undefined ? '' : text.slice(written.start, written.end);
|
|
68
|
+
// A field left empty names nothing, which is the same answer as a name nothing declares.
|
|
69
|
+
if (target === '')
|
|
70
|
+
return NONE;
|
|
71
|
+
const declarations = [];
|
|
72
|
+
const seen = new Set();
|
|
73
|
+
for (const declared of declaringObjects(document, schema, new Set(lists), fold(target))) {
|
|
74
|
+
const region = locate(text, schema, declared);
|
|
75
|
+
if (region === undefined || seen.has(region.start))
|
|
76
|
+
continue;
|
|
77
|
+
seen.add(region.start);
|
|
78
|
+
declarations.push({ region, typeName: declared.typeName });
|
|
79
|
+
}
|
|
80
|
+
return { status: 'ok', declarations };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The reference lists a field points into, taken from `describeObjectType` (FR-029).
|
|
84
|
+
*
|
|
85
|
+
* With the one hole `completionsAt` documents at length: `describeObjectType` drops the type's
|
|
86
|
+
* positional first field, which on an anonymous type is a real field, so that one is read from its
|
|
87
|
+
* own record. Reading `ol` is the same key the description reports and not a second opinion about
|
|
88
|
+
* it.
|
|
89
|
+
*/
|
|
90
|
+
function listsPointedInto(schema, typeName, type, fieldName) {
|
|
91
|
+
const described = describeObjectType(schema, typeName).fields.find((field) => field.name === fieldName);
|
|
92
|
+
if (described !== undefined)
|
|
93
|
+
return described.objectList ?? [];
|
|
94
|
+
return type.p[fieldName]?.ol ?? [];
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The objects declaring `folded` into one of `wanted`.
|
|
98
|
+
*
|
|
99
|
+
* The two ways a name reaches a reference list, and no third one (FR-029). Both are read, because
|
|
100
|
+
* the second is how anonymous types such as `FluidProperties:Name` carry their identity, and
|
|
101
|
+
* treating those as nameless would report nothing where the model plainly declares something.
|
|
102
|
+
*
|
|
103
|
+
* Membership of the lists the field points into is what makes a match a declaration rather than a
|
|
104
|
+
* coincidence. A `Zone` and a `Construction` may both be called `Office`, and following a
|
|
105
|
+
* construction name to the zone would be the guess this function exists to avoid. It is why a name
|
|
106
|
+
* declared only into some other list reports none here while the validator's dangling check, which
|
|
107
|
+
* asks the coarser question of whether any object declares the name at all, stays quiet: this
|
|
108
|
+
* reports what the field can name, not what the file happens to contain.
|
|
109
|
+
*
|
|
110
|
+
* Names are compared case-insensitively, the way EnergyPlus resolves them and the way every other
|
|
111
|
+
* name comparison in this library does.
|
|
112
|
+
*/
|
|
113
|
+
function declaringObjects(document, schema, wanted, folded) {
|
|
114
|
+
const out = [];
|
|
115
|
+
for (const object of document.objects()) {
|
|
116
|
+
const canonical = schema.resolve(object.typeName) ?? object.typeName;
|
|
117
|
+
const type = schema.get(canonical);
|
|
118
|
+
if (type === undefined)
|
|
119
|
+
continue;
|
|
120
|
+
const nameIndex = type.f.indexOf('name');
|
|
121
|
+
if (nameIndex >= 0 &&
|
|
122
|
+
object.name !== '' &&
|
|
123
|
+
fold(object.name) === folded &&
|
|
124
|
+
contributes(type.nref, wanted)) {
|
|
125
|
+
out.push({ typeName: canonical, fieldIndex: nameIndex, text: object.name });
|
|
126
|
+
}
|
|
127
|
+
for (const [field, definition] of Object.entries(type.p)) {
|
|
128
|
+
if (!contributes(definition.ref, wanted))
|
|
129
|
+
continue;
|
|
130
|
+
const value = object.get(field);
|
|
131
|
+
if (typeof value !== 'string' || value === '' || fold(value) !== folded)
|
|
132
|
+
continue;
|
|
133
|
+
const fieldIndex = type.f.indexOf(field);
|
|
134
|
+
if (fieldIndex < 0)
|
|
135
|
+
continue;
|
|
136
|
+
out.push({ typeName: canonical, fieldIndex, text: value });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
/** Whether any list this record contributes to is one the field points into. */
|
|
142
|
+
function contributes(declared, wanted) {
|
|
143
|
+
if (declared === undefined)
|
|
144
|
+
return false;
|
|
145
|
+
return declared.some((list) => wanted.has(list));
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The region in the text where this object writes the name it declares.
|
|
149
|
+
*
|
|
150
|
+
* Every occurrence of the name is a candidate and a cursor context decides each one: the match
|
|
151
|
+
* must be a field, of the declaring type, at the declaring field's index, and the field's whole
|
|
152
|
+
* text must be the name rather than a value containing it, so `Office` never resolves to
|
|
153
|
+
* `Office Zone 2`. The first survivor is the answer, because a document cannot hold two objects of
|
|
154
|
+
* one type sharing a name: `IdfDocument.addRaw` throws on the second and `parseIdf` records a
|
|
155
|
+
* `ParseError` and skips it, which is the same fact the finding correlation rests on (research
|
|
156
|
+
* R6).
|
|
157
|
+
*
|
|
158
|
+
* The comparison is exact rather than folded because the string being looked for came out of the
|
|
159
|
+
* document, which stores a value as the text wrote it, trimmed and with comments stripped. The one
|
|
160
|
+
* case that misses is a name interrupted by a comment between two of its words, which the reader
|
|
161
|
+
* joins and the syntax layer does not; that declaration is then reported as unlocatable rather
|
|
162
|
+
* than as a region on the wrong characters.
|
|
163
|
+
*/
|
|
164
|
+
function locate(text, schema, declared) {
|
|
165
|
+
for (let from = text.indexOf(declared.text); from >= 0; from = text.indexOf(declared.text, from + 1)) {
|
|
166
|
+
const context = contextAt(text, from, schema);
|
|
167
|
+
if (context.at !== 'field' || context.fieldIndex !== declared.fieldIndex)
|
|
168
|
+
continue;
|
|
169
|
+
if (context.typeName !== declared.typeName)
|
|
170
|
+
continue;
|
|
171
|
+
const region = context.statement.fields[declared.fieldIndex];
|
|
172
|
+
if (region === undefined)
|
|
173
|
+
continue;
|
|
174
|
+
if (text.slice(region.start, region.end) !== declared.text)
|
|
175
|
+
continue;
|
|
176
|
+
return region;
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
/** Case-insensitive name comparison, as EnergyPlus resolves names. */
|
|
181
|
+
function fold(value) {
|
|
182
|
+
return value.toLowerCase();
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=declaration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declaration.js","sourceRoot":"","sources":["../src/declaration.ts"],"names":[],"mappings":"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"}
|