@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/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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"}