@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.
@@ -0,0 +1,97 @@
1
+ import { type DocsUrl, type FieldDescription, type ProsePool, type Region, type Schema } from '@idfkit/core';
2
+ /**
3
+ * What the schema says about whatever the offset is on.
4
+ *
5
+ * Every member is reported rather than composed. The facts come from
6
+ * `describeObjectType`, the prose from the pool the caller supplied, and the manual location from
7
+ * `docsUrlForObject`; nothing here is derived from a field's name, which is the one thing FR-022
8
+ * forbids by name.
9
+ */
10
+ export interface Explanation {
11
+ /**
12
+ * The region this explanation describes (FR-049).
13
+ *
14
+ * What a consumer highlights while the explanation is shown, so that the reader can see which
15
+ * characters the words are about. Carried rather than left to the consumer for the reason offers
16
+ * carry theirs: an editor's own word rules split `BuildingSurface:Detailed` at the colon and
17
+ * `Office Zone 1` at the spaces, and would highlight a fragment of each.
18
+ *
19
+ * It identifies a thing rather than something to draw, so it stays whole and may cross a line
20
+ * boundary (FR-050): a field written across two lines is ordinary in this format.
21
+ */
22
+ readonly region: Region;
23
+ /** What the offset is on. */
24
+ readonly of: 'objectType' | 'field';
25
+ /** The canonical type name. */
26
+ readonly typeName: string;
27
+ /** The schema field name, when `of` is `'field'`. */
28
+ readonly fieldName: string | undefined;
29
+ /**
30
+ * The schema's own prose, when the caller supplied the pool.
31
+ *
32
+ * The type's memo for a type name and the field's note for a field. `undefined` where no pool
33
+ * was supplied and where the pool carries none for this record: absence is reported as absence,
34
+ * never filled with a sentence made up from the name.
35
+ */
36
+ readonly prose: string | undefined;
37
+ /**
38
+ * The field's facts, exactly as `describeObjectType` produces them. Undefined for a type name.
39
+ *
40
+ * Also undefined for one field this package cannot describe, and it is worth naming rather than
41
+ * hiding. `describeObjectType` drops the type's positional first field, because Python's
42
+ * `get_field_names` drops it on the assumption that it is always the name. On the anonymous
43
+ * types it is a real field, and `GlobalGeometryRules.starting_vertex_position` is one an author
44
+ * edits by hand. Rebuilding a `FieldDescription` here would be a second copy of the schema's
45
+ * facts, which FR-029 forbids and which would drift from the reference page the reader is
46
+ * comparing against, so the structural facts are reported as absent and the field's own prose is
47
+ * still resolved. Closing the hole properly means changing `describeObjectType` in
48
+ * `@idfkit/core`, which this package does not own.
49
+ */
50
+ readonly field: FieldDescription | undefined;
51
+ /**
52
+ * Where the manual documents this, from `docsUrlForObject`.
53
+ *
54
+ * The type's page in both cases, because that is where the manual documents a field: it has no
55
+ * page of its own. `undefined` for a version the documentation site does not carry.
56
+ */
57
+ readonly docs: DocsUrl | undefined;
58
+ }
59
+ /**
60
+ * What the schema says here, or why it says nothing.
61
+ *
62
+ * The same discriminated shape as `CompletionResult`, for the same reason: "there is nothing here
63
+ * to explain" and "I could not consult a schema" are different states, and an editor that rendered
64
+ * them identically would teach the reader that the tool is broken in the first case and silently
65
+ * wrong in the second (FR-031).
66
+ */
67
+ export type ExplanationResult = {
68
+ readonly status: 'ok';
69
+ readonly explanation: Explanation;
70
+ } | {
71
+ readonly status: 'noSchema';
72
+ } | {
73
+ readonly status: 'unknownType';
74
+ readonly typeName: string;
75
+ } | {
76
+ readonly status: 'notApplicable';
77
+ };
78
+ /**
79
+ * What the thing under the cursor means, in the schema's own words.
80
+ *
81
+ * Bounded local work, like every other cursor answer: the cursor is placed by {@link contextAt},
82
+ * which scans one statement, and the answer then comes from the type, whose cost is the type
83
+ * rather than the file.
84
+ *
85
+ * The prose pool stays the caller's to load (FR-028). It is a parameter rather than something
86
+ * reached for, and this module imports it as a type alone, so a caller who never asks for prose
87
+ * never pays for it: passing nothing yields the structural facts with `prose: undefined`, which is
88
+ * a truthful absence rather than a sentence derived from the field's name.
89
+ *
90
+ * `schema` is written as possibly absent rather than required, because `'noSchema'` is a state
91
+ * FR-031 requires this to report and a signature that forbade the input would make it unreachable
92
+ * from typed code.
93
+ *
94
+ * Nothing throws, for any input.
95
+ */
96
+ export declare function explainAt(text: string, offset: number, schema: Schema | undefined, prose?: ProsePool): ExplanationResult;
97
+ //# sourceMappingURL=explain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../src/explain.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,MAAM,EACZ,MAAM,cAAc,CAAC;AAItB;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC;IACpC,+BAA+B;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC7C;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;CAAE,GAC5D;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;AAKzC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,CAAC,EAAE,SAAS,GAChB,iBAAiB,CAoEnB"}
@@ -0,0 +1,130 @@
1
+ import { describeObjectType, docsUrlForObject, } from '@idfkit/core';
2
+ import { contextAt } from './cursor.js';
3
+ const NOT_APPLICABLE = { status: 'notApplicable' };
4
+ const NO_SCHEMA = { status: 'noSchema' };
5
+ /**
6
+ * What the thing under the cursor means, in the schema's own words.
7
+ *
8
+ * Bounded local work, like every other cursor answer: the cursor is placed by {@link contextAt},
9
+ * which scans one statement, and the answer then comes from the type, whose cost is the type
10
+ * rather than the file.
11
+ *
12
+ * The prose pool stays the caller's to load (FR-028). It is a parameter rather than something
13
+ * reached for, and this module imports it as a type alone, so a caller who never asks for prose
14
+ * never pays for it: passing nothing yields the structural facts with `prose: undefined`, which is
15
+ * a truthful absence rather than a sentence derived from the field's name.
16
+ *
17
+ * `schema` is written as possibly absent rather than required, because `'noSchema'` is a state
18
+ * FR-031 requires this to report and a signature that forbade the input would make it unreachable
19
+ * from typed code.
20
+ *
21
+ * Nothing throws, for any input.
22
+ */
23
+ export function explainAt(text, offset, schema, prose) {
24
+ const context = contextAt(text, offset, schema);
25
+ // Inside a comment, and in the whitespace between two statements, there is nothing the schema
26
+ // has anything to say about. Reporting `'noSchema'` here would send a caller off to load one
27
+ // that would change this answer not at all.
28
+ if (context.at === 'comment' || context.at === 'betweenStatements')
29
+ return NOT_APPLICABLE;
30
+ if (schema === undefined)
31
+ return NO_SCHEMA;
32
+ // The region first, and the schema afterwards. An offset on a separator, on a terminator, or in
33
+ // the whitespace beside a value falls outside every region this statement holds, and answering
34
+ // it with the nearest field is the behaviour that makes a hover feel haunted (FR-049). The test
35
+ // is containment rather than a character test, because a region already ends where the value
36
+ // ends: it begins at the value's first non-blank character and stops after its last, so
37
+ // everything between two values, the comma included, is outside both.
38
+ const at = clampOffset(offset, text.length);
39
+ const region = describedRegion(context, at);
40
+ if (region === undefined)
41
+ return NOT_APPLICABLE;
42
+ const typeName = context.typeName;
43
+ if (typeName === undefined) {
44
+ return { status: 'unknownType', typeName: context.statement.typeNameText };
45
+ }
46
+ const type = schema.get(typeName);
47
+ // Unreachable through `resolve`, which only returns a name the schema holds, and kept because
48
+ // `describeObjectType` throws on a type the schema lacks and nothing here may throw (FR-030).
49
+ if (type === undefined)
50
+ return { status: 'unknownType', typeName };
51
+ const described = describeObjectType(schema, typeName, prose);
52
+ const docs = docsUrlForObject(typeName, schema.version, schema);
53
+ if (context.at === 'typeName') {
54
+ return {
55
+ status: 'ok',
56
+ explanation: {
57
+ region,
58
+ of: 'objectType',
59
+ typeName,
60
+ fieldName: undefined,
61
+ prose: described.memo,
62
+ field: undefined,
63
+ docs,
64
+ },
65
+ };
66
+ }
67
+ const fieldName = context.fieldName;
68
+ // Past the type's last field, with no extensible group to repeat. The schema defines no field
69
+ // here and so says nothing about one; that the field should not be there is a finding, and
70
+ // saying so is that finding's job rather than this one's.
71
+ if (fieldName === undefined)
72
+ return NOT_APPLICABLE;
73
+ const field = described.fields.find((candidate) => candidate.name === fieldName);
74
+ return {
75
+ status: 'ok',
76
+ explanation: {
77
+ region,
78
+ of: 'field',
79
+ typeName,
80
+ fieldName,
81
+ // `field.note` is the same lookup, and taking it from the description keeps one source for
82
+ // it. The fallback is the pool lookup for the one field the description cannot carry, which
83
+ // resolves an index the schema already holds rather than reproducing anything.
84
+ prose: field === undefined ? proseFor(type.p[fieldName]?.n, prose) : field.note,
85
+ field,
86
+ docs,
87
+ },
88
+ };
89
+ }
90
+ /**
91
+ * The region the explanation is about, or `undefined` when the offset is not on one.
92
+ *
93
+ * `at` is `'typeName'` or `'field'` by the time this is called, so the region is the statement's
94
+ * type name or the written field at the cursor's index. A field written empty has an empty region
95
+ * positioned between its separators, which contains no offset at all, and that is the right answer
96
+ * for it: there is no text there to explain.
97
+ */
98
+ function describedRegion(context, offset) {
99
+ const region = context.at === 'typeName'
100
+ ? context.statement.typeName
101
+ : // `fieldIndex` is defined for every `'field'` context; the guard is what the compiler
102
+ // needs, and an offset on no field is not on a region either way.
103
+ context.statement.fields[context.fieldIndex ?? -1];
104
+ if (region === undefined)
105
+ return undefined;
106
+ return offset >= region.start && offset < region.end ? region : undefined;
107
+ }
108
+ /** Resolve a prose index against the pool. Nothing is hydrated when no pool was supplied. */
109
+ function proseFor(index, prose) {
110
+ if (index === undefined || prose === undefined)
111
+ return undefined;
112
+ return prose[index];
113
+ }
114
+ /**
115
+ * Into `[0, max]`, whole. `NaN` lands at 0, since no position is nearer than another.
116
+ *
117
+ * The clamp `contextAt` applies (FR-032), repeated because it is private to the cursor module. A
118
+ * containment test measured against an unclamped offset would disagree with the context it is
119
+ * testing, and would report nothing for an offset a keystroke past the end of the text where the
120
+ * cursor answer describes the last character.
121
+ */
122
+ function clampOffset(value, max) {
123
+ if (Number.isNaN(value))
124
+ return 0;
125
+ const whole = Math.trunc(value);
126
+ if (whole < 0)
127
+ return 0;
128
+ return whole > max ? max : whole;
129
+ }
130
+ //# sourceMappingURL=explain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.js","sourceRoot":"","sources":["../src/explain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import type { ParseDiagnostic, Region, Schema, SyntaxLayer, ValidationError } from '@idfkit/core';
2
+ /**
3
+ * An existing finding with the region it concerns attached. Not a new finding.
4
+ *
5
+ * `F` stays whatever it was: a `ParseDiagnostic` or a `ValidationError`, unchanged, with two
6
+ * properties added beside it. The generic is what lets both travel through one path without either
7
+ * type being touched, which is the whole point of correlating here rather than at the source. FR-014
8
+ * forbids filtering or rewording a finding and FR-015 forbids changing what an existing caller
9
+ * receives; both hold structurally, because nothing on the read path knows this module exists.
10
+ */
11
+ export type PositionedFinding<F> = F & {
12
+ /** The region the finding concerns. */
13
+ readonly region: Region;
14
+ /** Whether the region selects the field or falls back to the statement. */
15
+ readonly precision: 'field' | 'statement';
16
+ };
17
+ /**
18
+ * Read text, validate what it produced, and position every finding from both.
19
+ *
20
+ * One parse, one validation, one scan, in that order, and then the correlation below. A consumer
21
+ * that already holds findings from its own run should call {@link position} instead and keep its
22
+ * parse; this exists for the consumer that has only text.
23
+ *
24
+ * Reading findings come first and validation findings after, which is the order in which the two
25
+ * runs produced them. Neither list is filtered: a value that is both unreadable and invalid is two
26
+ * findings here because it was two findings before.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * for (const finding of findingsIn(text, schema)) {
31
+ * const { line, column } = lineColumnAt({ text }, finding.region.start);
32
+ * console.log(`${line}:${column}`, finding.message);
33
+ * }
34
+ * ```
35
+ */
36
+ export declare function findingsIn(text: string, schema: Schema): readonly PositionedFinding<ParseDiagnostic | ValidationError>[];
37
+ /**
38
+ * Attach a region to each of a set of findings already produced.
39
+ *
40
+ * Exposed separately from {@link findingsIn} so a consumer holding findings from its own parse and
41
+ * its own validation pays for one scan rather than for a second read of the same text.
42
+ *
43
+ * Two families of finding arrive here and they are positioned by different routes, because they
44
+ * know different things. A reading finding was made by the scanner and already names a line, and
45
+ * often a column, so it is placed directly at what the scanner saw. A validation finding was made
46
+ * from a document and names an object by type and name, never a place, so it is correlated against
47
+ * the statement index below.
48
+ *
49
+ * A finding of neither shape, or one naming something this text does not contain, still comes back
50
+ * with a region: an empty one at the start of the text, saying as little as is actually known. SC-004
51
+ * asks for a region on every finding and zero omitted, and dropping the ones that were hard would
52
+ * satisfy the count while defeating the point.
53
+ *
54
+ * Findings are returned in the order they were given, so a caller can zip the result against its
55
+ * own list.
56
+ */
57
+ export declare function position<F extends object>(findings: readonly F[], layer: SyntaxLayer, schema: Schema): readonly PositionedFinding<F>[];
58
+ //# sourceMappingURL=findings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findings.d.ts","sourceRoot":"","sources":["../src/findings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,MAAM,EACN,MAAM,EAGN,WAAW,EACX,eAAe,EAChB,MAAM,cAAc,CAAC;AAEtB;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI,CAAC,GAAG;IACrC,uCAAuC;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,WAAW,CAAC;CAC3C,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,SAAS,iBAAiB,CAAC,eAAe,GAAG,eAAe,CAAC,EAAE,CAYjE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EACvC,QAAQ,EAAE,SAAS,CAAC,EAAE,EACtB,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,GACb,SAAS,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAiBjC"}
@@ -0,0 +1,325 @@
1
+ import { offsetAt, parseIdf, scanIdf, validateDocument } from '@idfkit/core';
2
+ /**
3
+ * Read text, validate what it produced, and position every finding from both.
4
+ *
5
+ * One parse, one validation, one scan, in that order, and then the correlation below. A consumer
6
+ * that already holds findings from its own run should call {@link position} instead and keep its
7
+ * parse; this exists for the consumer that has only text.
8
+ *
9
+ * Reading findings come first and validation findings after, which is the order in which the two
10
+ * runs produced them. Neither list is filtered: a value that is both unreadable and invalid is two
11
+ * findings here because it was two findings before.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * for (const finding of findingsIn(text, schema)) {
16
+ * const { line, column } = lineColumnAt({ text }, finding.region.start);
17
+ * console.log(`${line}:${column}`, finding.message);
18
+ * }
19
+ * ```
20
+ */
21
+ export function findingsIn(text, schema) {
22
+ // Never strict: a strict read throws on the first finding, and a caller asking for every finding
23
+ // in a file is asking for the file to be described rather than rejected.
24
+ const { document, diagnostics } = parseIdf(text, schema, { strict: false });
25
+ const validation = validateDocument(document);
26
+ const findings = [
27
+ ...diagnostics,
28
+ ...validation.errors,
29
+ ...validation.warnings,
30
+ ...validation.info,
31
+ ];
32
+ return position(findings, scanIdf(text), schema);
33
+ }
34
+ /**
35
+ * Attach a region to each of a set of findings already produced.
36
+ *
37
+ * Exposed separately from {@link findingsIn} so a consumer holding findings from its own parse and
38
+ * its own validation pays for one scan rather than for a second read of the same text.
39
+ *
40
+ * Two families of finding arrive here and they are positioned by different routes, because they
41
+ * know different things. A reading finding was made by the scanner and already names a line, and
42
+ * often a column, so it is placed directly at what the scanner saw. A validation finding was made
43
+ * from a document and names an object by type and name, never a place, so it is correlated against
44
+ * the statement index below.
45
+ *
46
+ * A finding of neither shape, or one naming something this text does not contain, still comes back
47
+ * with a region: an empty one at the start of the text, saying as little as is actually known. SC-004
48
+ * asks for a region on every finding and zero omitted, and dropping the ones that were hard would
49
+ * satisfy the count while defeating the point.
50
+ *
51
+ * Findings are returned in the order they were given, so a caller can zip the result against its
52
+ * own list.
53
+ */
54
+ export function position(findings, layer, schema) {
55
+ // Built per call rather than cached against the layer: it costs one pass over the statements and
56
+ // a couple of string operations each, against a parse and a validation run that have already
57
+ // happened. A cache here would be state, and this package holds none.
58
+ const index = indexStatements(layer, schema);
59
+ return findings.map((finding) => {
60
+ const placed = isValidationShaped(finding)
61
+ ? placeValidation(finding, occurrenceOf(finding), schema, index)
62
+ : isParseShaped(finding)
63
+ ? placeParse(finding, layer)
64
+ : undefined;
65
+ return {
66
+ ...finding,
67
+ region: placed?.region ?? NOWHERE,
68
+ precision: placed?.precision ?? 'statement',
69
+ };
70
+ });
71
+ }
72
+ /**
73
+ * Where a finding lands when nothing in the text answers to it.
74
+ *
75
+ * Empty and at the start, which is what an editor renders as a file-level diagnostic. A region
76
+ * covering the whole text would be a claim about where the problem is, and there is no such claim
77
+ * to make.
78
+ */
79
+ const NOWHERE = { start: 0, end: 0 };
80
+ function indexStatements(layer, schema) {
81
+ const byName = new Map();
82
+ const byOrdinal = new Map();
83
+ // The schema hydrates a definition on every `get`, and a file holds thousands of statements of a
84
+ // few hundred types, so the answer to "is this type named?" is worth keeping.
85
+ const named = new Map();
86
+ for (const statement of layer.statements) {
87
+ const type = fold(statement.typeNameText);
88
+ let ofType = byOrdinal.get(type);
89
+ if (ofType === undefined) {
90
+ ofType = [];
91
+ byOrdinal.set(type, ofType);
92
+ }
93
+ ofType.push(statement);
94
+ let isNamed = named.get(type);
95
+ if (isNamed === undefined) {
96
+ isNamed = schema.get(statement.typeNameText)?.anon !== 1;
97
+ named.set(type, isNamed);
98
+ }
99
+ if (!isNamed)
100
+ continue;
101
+ // The name is the first field after the type name, which is what `Statement.fields` indexes
102
+ // from zero and what `parseIdf` reads the object's name out of.
103
+ const written = statement.fields[0];
104
+ if (written === undefined)
105
+ continue;
106
+ const key = nameKey(type, fold(layer.text.slice(written.start, written.end).trim()));
107
+ // First occurrence wins, matching `addRaw`: the object the document kept is the first one
108
+ // written, so a finding about this type and name is a finding about this statement.
109
+ if (!byName.has(key))
110
+ byName.set(key, statement);
111
+ }
112
+ return { byName, byOrdinal };
113
+ }
114
+ // ---------------------------------------------------------------------------
115
+ // Validation findings, which name an object and are correlated
116
+ // ---------------------------------------------------------------------------
117
+ function placeValidation(finding, occurrence, schema, index) {
118
+ const statement = correlate(finding, occurrence, index);
119
+ if (statement === undefined)
120
+ return undefined;
121
+ // A finding with no field concerns the object itself: an unknown type, a singleton written twice.
122
+ // It selects the type name, which is the part of the statement that identifies it and the only
123
+ // part short enough to underline.
124
+ if (finding.field === undefined) {
125
+ return { region: statement.typeName, precision: 'statement' };
126
+ }
127
+ const type = schema.get(finding.objType);
128
+ const at = type === undefined ? undefined : fieldIndexOf(type, finding.field, occurrence.index);
129
+ // A field the schema does not define, or one whose repeat the finding could not say, falls back
130
+ // to the whole statement and says so. Guessing a position for it would put an underline under a
131
+ // value that is not the one complained about, which reads as correct and is not.
132
+ if (at === undefined)
133
+ return { region: statement.region, precision: 'statement' };
134
+ const region = statement.fields[at];
135
+ // Past what was written: the field is absent from the text, which is exactly what a missing
136
+ // required field is. There is nothing to select, so the statement stands in.
137
+ if (region === undefined)
138
+ return { region: statement.region, precision: 'statement' };
139
+ // A field written blank keeps an empty region between its two commas, and that is still the right
140
+ // place: it is where the value would have gone, and where an editor puts the caret to type it.
141
+ return { region, precision: 'field' };
142
+ }
143
+ /** The statement a validation finding is about, by name when it has one and by ordinal when it does not. */
144
+ function correlate(finding, occurrence, index) {
145
+ const type = fold(finding.objType);
146
+ if (finding.objName !== '') {
147
+ const named = index.byName.get(nameKey(type, fold(finding.objName)));
148
+ if (named !== undefined)
149
+ return named;
150
+ }
151
+ const ofType = index.byOrdinal.get(type);
152
+ if (ofType === undefined)
153
+ return undefined;
154
+ // `objName` is documented as empty for anonymous objects, which is the signal to use the ordinal.
155
+ // The finding does not carry one, so a caller that knows it supplies it and everything else lands
156
+ // on the first statement of the type. That is exact for the singletons nearly every anonymous
157
+ // type is, and it is the closest available answer for the few that are not.
158
+ return ofType[occurrence.ordinal ?? 0];
159
+ }
160
+ /**
161
+ * The positional index a schema field name occupies, or `undefined` when the schema has no answer.
162
+ *
163
+ * Positional order is `SlimType.f`, which is the IDD order and includes the name at index 0 for a
164
+ * named type. `Statement.fields` is indexed from the first field after the type name, so a fixed
165
+ * field's index into it is simply its position in `f` for both named and anonymous types: on a
166
+ * named type index 0 is the name, and on an anonymous one index 0 is the first real field, which is
167
+ * what `f` holds in each case.
168
+ *
169
+ * An extensible field lives in the repeat group instead, which begins where the fixed fields end.
170
+ * The arithmetic is the one place this can go wrong quietly: a finding about the ninth vertex of a
171
+ * surface positioned at the first looks plausible in a screenshot and is useless in an editor. Worked
172
+ * on `BuildingSurface:Detailed`, whose eleven fixed fields are followed by repeats three wide, the
173
+ * x coordinate of the ninth vertex is `11 + 8 * 3 + 0`, which is field 35 of the statement.
174
+ */
175
+ function fieldIndexOf(type, field, repeat) {
176
+ const fixed = type.f.indexOf(field);
177
+ if (fixed !== -1)
178
+ return fixed;
179
+ const extensible = type.x;
180
+ if (extensible === undefined)
181
+ return undefined;
182
+ const offsetWithinGroup = extensible.fields.indexOf(field);
183
+ if (offsetWithinGroup === -1)
184
+ return undefined;
185
+ // Which repeat is not something a `ValidationError` can say, and a wrong repeat is worse than no
186
+ // position at all, so without one this declines to answer and the caller falls back to the
187
+ // statement.
188
+ if (repeat === undefined)
189
+ return undefined;
190
+ const fixedCount = type.f.length;
191
+ const groupWidth = extensible.fields.length;
192
+ return fixedCount + repeat * groupWidth + offsetWithinGroup;
193
+ }
194
+ // ---------------------------------------------------------------------------
195
+ // Reading findings, which name a place and are used as they are
196
+ // ---------------------------------------------------------------------------
197
+ /**
198
+ * Where a reading finding sits, from the line and column the scanner recorded.
199
+ *
200
+ * Correlation is not used and must not be: a duplicate-name finding names a type and a name that
201
+ * belong to the statement ABOVE the one it is about, so correlating it would underline the wrong
202
+ * object. The scanner saw the offending statement and said where it was, and that answer is better
203
+ * than any reconstruction of it.
204
+ *
205
+ * A finding whose column resolves to the statement's own first character is about the statement, and
206
+ * every finding `lex` and `parseIdf` produce about a statement carries one. A finding carrying only
207
+ * a line is about a field on that line, which is what the `InvalidField` diagnostic is; it becomes
208
+ * field-precise when exactly one field begins on that line, and stays statement-precise when several
209
+ * do, because then which one is meant is not recoverable.
210
+ */
211
+ function placeParse(finding, layer) {
212
+ const span = lineSpan(layer, finding.line);
213
+ const statement = statementNear(layer, span);
214
+ if (statement === undefined)
215
+ return undefined;
216
+ if (finding.column !== undefined) {
217
+ const at = offsetAt(layer, { line: finding.line, column: finding.column });
218
+ if (at === statement.region.start) {
219
+ return { region: statement.typeName, precision: 'statement' };
220
+ }
221
+ }
222
+ const field = soleFieldOn(statement, span);
223
+ return field === undefined
224
+ ? { region: statement.region, precision: 'statement' }
225
+ : { region: field, precision: 'field' };
226
+ }
227
+ /** The statement a line falls in, or the one beginning later on it when the line falls between two. */
228
+ function statementNear(layer, span) {
229
+ const statements = layer.statements;
230
+ const at = lastStartingAtOrBefore(statements, span.start);
231
+ const containing = at === -1 ? undefined : statements[at];
232
+ if (containing !== undefined && span.start < containing.region.end)
233
+ return containing;
234
+ // Between two statements, which is where a finding on the blank line above a statement lands.
235
+ const next = statements[at + 1];
236
+ return next !== undefined && next.region.start <= span.end ? next : containing;
237
+ }
238
+ /** The one field beginning on this line, or `undefined` when none or several do. */
239
+ function soleFieldOn(statement, span) {
240
+ let found;
241
+ for (const field of statement.fields) {
242
+ if (field.start < span.start || field.start > span.end)
243
+ continue;
244
+ if (found !== undefined)
245
+ return undefined;
246
+ found = field;
247
+ }
248
+ return found;
249
+ }
250
+ /** Index of the last statement beginning at or before `offset`, or -1 when none does. */
251
+ function lastStartingAtOrBefore(statements, offset) {
252
+ let low = 0;
253
+ let high = statements.length - 1;
254
+ let found = -1;
255
+ while (low <= high) {
256
+ const middle = (low + high) >> 1;
257
+ if (statements[middle].region.start <= offset) {
258
+ found = middle;
259
+ low = middle + 1;
260
+ }
261
+ else {
262
+ high = middle - 1;
263
+ }
264
+ }
265
+ return found;
266
+ }
267
+ /**
268
+ * The offsets a 1-based line begins and ends at, the ending one being the line break itself.
269
+ *
270
+ * `offsetAt` clamps a column past the end of its line to that break, so asking for an impossible
271
+ * column is how the end is found without a second index of the text.
272
+ */
273
+ function lineSpan(layer, line) {
274
+ return {
275
+ start: offsetAt(layer, { line, column: 1 }),
276
+ end: offsetAt(layer, { line, column: Number.MAX_SAFE_INTEGER }),
277
+ };
278
+ }
279
+ // ---------------------------------------------------------------------------
280
+ // Telling the two shapes apart
281
+ // ---------------------------------------------------------------------------
282
+ /**
283
+ * A `ValidationError` names an object type; nothing else this positions does.
284
+ *
285
+ * Structural rather than nominal because `position` is generic over the finding, which is what keeps
286
+ * both types unmodified. The two shapes are disjoint on this property in both libraries.
287
+ */
288
+ function isValidationShaped(finding) {
289
+ return typeof finding.objType === 'string';
290
+ }
291
+ /** A `ParseDiagnostic` names a line. Checked after the above, since a validation finding names none. */
292
+ function isParseShaped(finding) {
293
+ return typeof finding.line === 'number';
294
+ }
295
+ /** Whatever a caller attached to say which occurrence a finding is about. See {@link Occurrence}. */
296
+ function occurrenceOf(finding) {
297
+ const carried = finding;
298
+ return {
299
+ ordinal: typeof carried.ordinal === 'number' ? carried.ordinal : undefined,
300
+ index: typeof carried.index === 'number' ? carried.index : undefined,
301
+ };
302
+ }
303
+ /** Case folding, as EnergyPlus resolves a type name and as `IdfCollection` keys a name. */
304
+ function fold(value) {
305
+ return value.toLowerCase();
306
+ }
307
+ /**
308
+ * The two halves of the name key, joined by a character neither of them can contain.
309
+ *
310
+ * An object name routinely holds spaces, commas and punctuation, so the separator is the one
311
+ * character IDF text cannot carry at all. Joining on anything a name may hold would let two
312
+ * different pairs produce one key.
313
+ */
314
+ function nameKey(type, name) {
315
+ return `${type}${SEPARATOR}${name}`;
316
+ }
317
+ /**
318
+ * The NUL character itself, written as a constant rather than as an escape inside the template.
319
+ *
320
+ * `\u0000` inside a template literal needs one backslash, and a second one turns it into the
321
+ * six printable characters `\u0000`, which a name may perfectly well contain: the join would then
322
+ * be ambiguous in exactly the way the doc comment above says it is not.
323
+ */
324
+ const SEPARATOR = '\u0000';
325
+ //# sourceMappingURL=findings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findings.js","sourceRoot":"","sources":["../src/findings.ts"],"names":[],"mappings":"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"}