@markii/stdlib 0.13.0 → 0.15.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,45 @@
1
+ /**
2
+ * The neutral shape of a render-time diagnostic event (docs/integration.md):
3
+ * something a renderer recognized and handled by rendering a quiet marker
4
+ * instead of silently dropping content or failing outright, reported
5
+ * through a render engine's `onDiagnostic` option so a host can route it to
6
+ * its own diagnostics surface (AGENTS.md's "clean is not silent" rule: a
7
+ * quiet in-page marker alone is not enough, the reason must also reach a
8
+ * surface a host actually shows).
9
+ *
10
+ * Both platform renderers (`@markii/react`, `@markii/html`) emit this exact
11
+ * shape from their own independent implementations, so a host wiring up
12
+ * one engine's callback needs no engine-specific branching if it later
13
+ * adds the other. The WORDING in `message` is produced by each engine's own
14
+ * `failure-presentation` module (the one home of failure wording per
15
+ * engine) — this module only defines the envelope, never the words.
16
+ */
17
+ /**
18
+ * `'invalid-attribute-value'` — a known attribute (one `@markii/stdlib`'s
19
+ * component contracts declare) received a value outside its closed enum;
20
+ * the component still rendered, with a quiet marker.
21
+ *
22
+ * `'unsafe-image-src'` — a component-built `<img>` (the standard `figure`)
23
+ * had its `src` refused as an unsafe URL scheme; the component rendered
24
+ * without the image, with a quiet marker.
25
+ */
26
+ export type DiagnosticKind = 'invalid-attribute-value' | 'unsafe-image-src';
27
+ export interface DiagnosticEvent {
28
+ readonly kind: DiagnosticKind;
29
+ /** The directive name the event happened on, when there is a single unambiguous one. */
30
+ readonly directive?: string;
31
+ /** The attribute name involved, for `'invalid-attribute-value'`. Absent for a kind with no single attribute to name. */
32
+ readonly attribute?: string;
33
+ /** The same human-readable text the in-page marker's tooltip carries. */
34
+ readonly message: string;
35
+ }
36
+ export type OnDiagnostic = (event: DiagnosticEvent) => void;
37
+ /**
38
+ * Calls `onDiagnostic` with `event`, swallowing any exception it throws.
39
+ * A host-supplied callback must never be able to break rendering — the same
40
+ * never-throw guarantee every other render-time hook in this format carries
41
+ * (Architecture rule 3's spirit, extended to a host's own code). Both
42
+ * engines call this instead of invoking `onDiagnostic` directly, so the
43
+ * guard cannot drift between them.
44
+ */
45
+ export declare function reportDiagnostic(onDiagnostic: OnDiagnostic | undefined, event: DiagnosticEvent): void;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The neutral shape of a render-time diagnostic event (docs/integration.md):
3
+ * something a renderer recognized and handled by rendering a quiet marker
4
+ * instead of silently dropping content or failing outright, reported
5
+ * through a render engine's `onDiagnostic` option so a host can route it to
6
+ * its own diagnostics surface (AGENTS.md's "clean is not silent" rule: a
7
+ * quiet in-page marker alone is not enough, the reason must also reach a
8
+ * surface a host actually shows).
9
+ *
10
+ * Both platform renderers (`@markii/react`, `@markii/html`) emit this exact
11
+ * shape from their own independent implementations, so a host wiring up
12
+ * one engine's callback needs no engine-specific branching if it later
13
+ * adds the other. The WORDING in `message` is produced by each engine's own
14
+ * `failure-presentation` module (the one home of failure wording per
15
+ * engine) — this module only defines the envelope, never the words.
16
+ */
17
+ /**
18
+ * Calls `onDiagnostic` with `event`, swallowing any exception it throws.
19
+ * A host-supplied callback must never be able to break rendering — the same
20
+ * never-throw guarantee every other render-time hook in this format carries
21
+ * (Architecture rule 3's spirit, extended to a host's own code). Both
22
+ * engines call this instead of invoking `onDiagnostic` directly, so the
23
+ * guard cannot drift between them.
24
+ */
25
+ export function reportDiagnostic(onDiagnostic, event) {
26
+ if (!onDiagnostic)
27
+ return;
28
+ try {
29
+ onDiagnostic(event);
30
+ }
31
+ catch {
32
+ // A throwing callback must never break rendering.
33
+ }
34
+ }
@@ -0,0 +1,15 @@
1
+ import type { InsertableComponent } from '../insert/component-catalog.js';
2
+ import type { CompletionContext, HoverInfo } from './types.js';
3
+ export type { CompletionContext, CompletionContextKind, CompletionItem, CompletionItemKind, ComponentDocumentation, HoverInfo, } from './types.js';
4
+ export { componentDocumentation, formatComponentDocumentation, } from './documentation.js';
5
+ /**
6
+ * The one entry point a host calls: the current line's text, the zero-based
7
+ * cursor column, and the insert catalog. Never throws; returns a `'none'`
8
+ * context when nothing completes here.
9
+ */
10
+ export declare function completionAt(line: string, column: number, catalog: readonly InsertableComponent[]): CompletionContext;
11
+ /**
12
+ * The directive under the cursor, for a hover popup. `undefined` when the
13
+ * cursor is not on a known component's directive name.
14
+ */
15
+ export declare function hoverAt(line: string, column: number, catalog: readonly InsertableComponent[]): HoverInfo | undefined;
@@ -0,0 +1,322 @@
1
+ import { getContract } from '../../contracts.js';
2
+ import { LAYOUT_ATTRIBUTES, LAYOUT_ATTRIBUTE_KEYS, layoutWrapperAxis, otherLayoutAxis, } from '../../layout.js';
3
+ import { componentSkeleton } from '../insert/component-skeleton.js';
4
+ import { firstSentence } from '../insert/first-sentence.js';
5
+ import { clampColumn, findDirectiveNameTokenAt, parseCompletionContext, } from './directive-context.js';
6
+ import { componentDocumentation } from './documentation.js';
7
+ export { componentDocumentation, formatComponentDocumentation, } from './documentation.js';
8
+ const EMPTY_CONTEXT_AT = (column) => ({
9
+ kind: 'none',
10
+ replaceStart: column,
11
+ replaceEnd: column,
12
+ items: [],
13
+ });
14
+ /** Whether `component` is offered when the author is typing directive `form`. A pack component with no declared kind (see `InsertableComponent.kindDeclared`) is offered in every form. */
15
+ function componentOffersForm(component, form) {
16
+ if (!component.kindDeclared)
17
+ return true;
18
+ return component.kind === form;
19
+ }
20
+ /**
21
+ * Adjusts a container skeleton's opening/closing fence to use exactly the
22
+ * colon run the user typed (docs/format.md: an outer fence needs more
23
+ * colons than an inner one, e.g. `::::tabs` when nesting). The skeleton's
24
+ * container form is always `:::name{...}\n\n:::` — a fixed three-colon
25
+ * prefix and a fixed three-colon suffix — so swapping both for `colonRun`
26
+ * is a straight slice-and-splice; `cursorOffset` shifts by the same amount
27
+ * the prefix grew, since the cursor always lands before the closing fence.
28
+ */
29
+ function adjustContainerFence(skeleton, colonRun) {
30
+ if (colonRun.length === 3)
31
+ return skeleton;
32
+ const shift = colonRun.length - 3;
33
+ const text = colonRun + skeleton.text.slice(3, skeleton.text.length - 3) + colonRun;
34
+ return { text, cursorOffset: skeleton.cursorOffset + shift };
35
+ }
36
+ function buildSkeleton(directiveName, kind, requiredAttributes, colonRun) {
37
+ const skeleton = componentSkeleton(directiveName, kind, requiredAttributes);
38
+ return kind === 'container'
39
+ ? adjustContainerFence(skeleton, colonRun)
40
+ : skeleton;
41
+ }
42
+ function directiveNameItem(component, ctx, isRestEmpty) {
43
+ let insertText;
44
+ let insertCursorOffset;
45
+ if (isRestEmpty) {
46
+ // The form the AUTHOR TYPED wins over the catalog's `kind` for a pack
47
+ // component that declared none: `componentOffersForm` above lets such a
48
+ // component through in all three forms, and the catalog's `'container'`
49
+ // is only a default there, not a fact. Building a container skeleton
50
+ // for an author who typed `::` would rewrite their two colons into a
51
+ // broken two-colon fence. For every other component the two agree by
52
+ // construction, since the filter only admits a matching form.
53
+ const kind = component.kindDeclared ? component.kind : ctx.form;
54
+ const skeleton = buildSkeleton(component.directiveName, kind, component.requiredAttributes, ctx.colonRun);
55
+ insertText = skeleton.text;
56
+ insertCursorOffset = skeleton.cursorOffset;
57
+ }
58
+ else {
59
+ insertText = component.directiveName;
60
+ insertCursorOffset = component.directiveName.length;
61
+ }
62
+ return {
63
+ label: component.directiveName,
64
+ kind: 'component',
65
+ detail: component.description ?? '',
66
+ documentation: componentDocumentation(component),
67
+ insertText,
68
+ insertCursorOffset,
69
+ group: component.group,
70
+ ...(component.packName !== undefined
71
+ ? { packName: component.packName }
72
+ : {}),
73
+ };
74
+ }
75
+ function directiveNameCompletionContext(ctx, catalog, line) {
76
+ const restOfLine = line.slice(ctx.replaceEnd);
77
+ const isRestEmpty = /^\s*$/.test(restOfLine);
78
+ const replaceStart = isRestEmpty
79
+ ? ctx.replaceStart
80
+ : ctx.replaceStart + ctx.colonRun.length;
81
+ const items = catalog
82
+ .filter((component) => componentOffersForm(component, ctx.form))
83
+ .map((component) => directiveNameItem(component, ctx, isRestEmpty));
84
+ return {
85
+ kind: 'directive-name',
86
+ replaceStart,
87
+ replaceEnd: ctx.replaceEnd,
88
+ tokenStart: ctx.tokenStart,
89
+ items,
90
+ };
91
+ }
92
+ /** The catalog entry for `directiveName`, matched case-insensitively the same way `hoverAt` matches. */
93
+ function findCatalogEntry(catalog, directiveName) {
94
+ const lowered = directiveName.toLowerCase();
95
+ return catalog.find((component) => component.directiveName.toLowerCase() === lowered);
96
+ }
97
+ /**
98
+ * The declared attributes of the pack component named `directiveName`, or
99
+ * an empty list when the name is unknown, is a standard component (its
100
+ * attributes come from its contract instead), or declared none.
101
+ *
102
+ * On a block form the two reserved layout attributes are filtered out:
103
+ * `width` and `align` are offered from `@markii/stdlib`'s one layout
104
+ * source a few lines below, so a pack that also declares one of those
105
+ * names would otherwise produce two rows for the same attribute, with
106
+ * different values behind them. An inline directive is never offered the
107
+ * layout attributes at all, so there a declared `width` is the pack's own
108
+ * and passes through.
109
+ */
110
+ function packAttributesFor(directiveName, form, catalog) {
111
+ const entry = findCatalogEntry(catalog, directiveName);
112
+ if (entry?.source !== 'pack' || entry.attributes === undefined)
113
+ return [];
114
+ if (form === 'inline')
115
+ return entry.attributes;
116
+ return entry.attributes.filter((attribute) => !LAYOUT_ATTRIBUTE_NAME_SET.has(attribute.name));
117
+ }
118
+ /**
119
+ * Expresses a pack's declared attribute as the same `AttributeSchema` a
120
+ * standard contract carries, so `attributeNameItem` below stays the ONE
121
+ * item builder for both sources rather than growing a near-duplicate for
122
+ * pack attributes. `default` has no place in `AttributeSchema` and is not
123
+ * invented into one here; it reaches the author through the attribute doc
124
+ * lines in `./documentation.ts`.
125
+ */
126
+ function schemaForPackAttribute(attribute) {
127
+ return {
128
+ type: 'string',
129
+ ...(attribute.required === true ? { required: true } : {}),
130
+ ...(attribute.values !== undefined ? { enum: attribute.values } : {}),
131
+ description: attribute.description ?? '',
132
+ };
133
+ }
134
+ function attributeNameItem(name, schema) {
135
+ const insertText = `${name}=""`;
136
+ const insertCursorOffset = name.length + 2;
137
+ const requiredPrefix = schema.required === true ? 'required. ' : '';
138
+ // A pack may declare an attribute with no description at all, which a
139
+ // standard contract never does. Trimming keeps that case from reading as
140
+ // `required. ` with a dangling separator, and is a no-op for every
141
+ // described attribute.
142
+ const detail = `${requiredPrefix}${firstSentence(schema.description)}`.trim();
143
+ return {
144
+ label: name,
145
+ kind: 'attribute',
146
+ detail,
147
+ documentation: { summary: schema.description, attributes: [], example: '' },
148
+ insertText,
149
+ insertCursorOffset,
150
+ };
151
+ }
152
+ /**
153
+ * The two reserved layout attributes, read from `@markii/stdlib` rather
154
+ * than re-listed here: that package is the one source of the layout
155
+ * vocabulary, and a third hand-copied literal is exactly what it exists to
156
+ * prevent.
157
+ */
158
+ const LAYOUT_ATTRIBUTE_NAMES = LAYOUT_ATTRIBUTE_KEYS;
159
+ const LAYOUT_ATTRIBUTE_NAME_SET = new Set(LAYOUT_ATTRIBUTE_NAMES);
160
+ /**
161
+ * Which reserved layout attributes to offer on `directiveName`, written in
162
+ * directive `form`.
163
+ *
164
+ * An inline directive gets none: the two keys are stripped there and have
165
+ * no effect, so offering them would be offering a no-op. A layout WRAPPER
166
+ * gets exactly one, the axis its own name did not already decide
167
+ * (docs/spec.md §3): `:::center` is already centered, so it offers `width`
168
+ * and not `align`, and `:::fit` offers `align` and not `width`. Writing the
169
+ * wrapper's own axis is ignored by both renderers, so completing it would
170
+ * be inviting the author to type something that does nothing. Every other
171
+ * block directive gets both.
172
+ */
173
+ function layoutAttributesFor(directiveName, form) {
174
+ if (form === 'inline')
175
+ return [];
176
+ const ownAxis = layoutWrapperAxis(directiveName);
177
+ return ownAxis === undefined
178
+ ? LAYOUT_ATTRIBUTE_NAMES
179
+ : [otherLayoutAxis(ownAxis)];
180
+ }
181
+ function attributeNameCompletionContext(ctx, catalog) {
182
+ const contract = getContract(ctx.directiveName);
183
+ const items = [];
184
+ const offered = new Set();
185
+ if (contract !== undefined) {
186
+ for (const [name, schema] of Object.entries(contract.attributes)) {
187
+ if (ctx.presentNames.has(name.toLowerCase()))
188
+ continue;
189
+ items.push(attributeNameItem(name, schema));
190
+ offered.add(name.toLowerCase());
191
+ }
192
+ }
193
+ else {
194
+ // No contract means this is not a standard component, so the only
195
+ // attribute metadata that can exist for it is what its pack declared
196
+ // (issue #27 slice 4). A pack that declared none simply contributes no
197
+ // rows, and the layout attributes below still apply.
198
+ for (const attribute of packAttributesFor(ctx.directiveName, ctx.form, catalog)) {
199
+ if (ctx.presentNames.has(attribute.name.toLowerCase()))
200
+ continue;
201
+ items.push(attributeNameItem(attribute.name, schemaForPackAttribute(attribute)));
202
+ offered.add(attribute.name.toLowerCase());
203
+ }
204
+ }
205
+ for (const name of layoutAttributesFor(ctx.directiveName, ctx.form)) {
206
+ if (ctx.presentNames.has(name) || offered.has(name))
207
+ continue;
208
+ items.push(attributeNameItem(name, LAYOUT_ATTRIBUTES[name]));
209
+ }
210
+ return {
211
+ kind: 'attribute-name',
212
+ replaceStart: ctx.replaceStart,
213
+ replaceEnd: ctx.replaceEnd,
214
+ items,
215
+ };
216
+ }
217
+ function resolveValueEnum(ctx, catalog) {
218
+ // The same axis filter the attribute-NAME context applies, so the two can
219
+ // never disagree: a name that is not offered here never gets values
220
+ // either, and `:::center{align=` completes nothing rather than offering
221
+ // three values the renderer will ignore.
222
+ if ((ctx.attributeName === 'width' || ctx.attributeName === 'align') &&
223
+ layoutAttributesFor(ctx.directiveName, ctx.form).includes(ctx.attributeName)) {
224
+ return LAYOUT_ATTRIBUTES[ctx.attributeName].enum;
225
+ }
226
+ const contract = getContract(ctx.directiveName);
227
+ if (contract === undefined) {
228
+ // A pack component's declared `values`, read through the same filtered
229
+ // accessor the attribute-name context uses, so a name offered from the
230
+ // layout source can never take its values from a pack declaration.
231
+ return packAttributesFor(ctx.directiveName, ctx.form, catalog).find((attribute) => attribute.name === ctx.attributeName)?.values;
232
+ }
233
+ // `Object.hasOwn`, not bare indexing: a directive attribute literally
234
+ // named `constructor` or `__proto__` must miss this lookup rather than
235
+ // resolve through the prototype chain to an inherited `Object.prototype`
236
+ // member. Same defense `getContract` itself applies one level up.
237
+ if (!Object.hasOwn(contract.attributes, ctx.attributeName))
238
+ return undefined;
239
+ return contract.attributes[ctx.attributeName]?.enum;
240
+ }
241
+ function attributeValueItem(value, ctx) {
242
+ let insertText;
243
+ if (ctx.quoteChar === undefined) {
244
+ insertText = value;
245
+ }
246
+ else if (ctx.hasClosingQuote) {
247
+ insertText = value;
248
+ }
249
+ else {
250
+ insertText = `${value}${ctx.quoteChar}`;
251
+ }
252
+ return {
253
+ label: value,
254
+ kind: 'value',
255
+ detail: '',
256
+ insertText,
257
+ insertCursorOffset: insertText.length,
258
+ };
259
+ }
260
+ function attributeValueCompletionContext(ctx, catalog) {
261
+ const enumValues = resolveValueEnum(ctx, catalog);
262
+ if (enumValues === undefined || enumValues.length === 0) {
263
+ return EMPTY_CONTEXT_AT(ctx.replaceEnd);
264
+ }
265
+ return {
266
+ kind: 'attribute-value',
267
+ replaceStart: ctx.replaceStart,
268
+ replaceEnd: ctx.replaceEnd,
269
+ items: enumValues.map((value) => attributeValueItem(value, ctx)),
270
+ };
271
+ }
272
+ /**
273
+ * The one entry point a host calls: the current line's text, the zero-based
274
+ * cursor column, and the insert catalog. Never throws; returns a `'none'`
275
+ * context when nothing completes here.
276
+ */
277
+ export function completionAt(line, column, catalog) {
278
+ try {
279
+ const safeLine = typeof line === 'string' ? line : '';
280
+ const safeCatalog = Array.isArray(catalog) ? catalog : [];
281
+ const clampedColumn = clampColumn(safeLine, column);
282
+ const ctx = parseCompletionContext(safeLine, clampedColumn);
283
+ switch (ctx.kind) {
284
+ case 'attribute-value':
285
+ return attributeValueCompletionContext(ctx, safeCatalog);
286
+ case 'attribute-name':
287
+ return attributeNameCompletionContext(ctx, safeCatalog);
288
+ case 'directive-name':
289
+ return directiveNameCompletionContext(ctx, safeCatalog, safeLine);
290
+ default:
291
+ return EMPTY_CONTEXT_AT(clampedColumn);
292
+ }
293
+ }
294
+ catch {
295
+ return EMPTY_CONTEXT_AT(clampColumn(line, column));
296
+ }
297
+ }
298
+ /**
299
+ * The directive under the cursor, for a hover popup. `undefined` when the
300
+ * cursor is not on a known component's directive name.
301
+ */
302
+ export function hoverAt(line, column, catalog) {
303
+ try {
304
+ const safeLine = typeof line === 'string' ? line : '';
305
+ const safeCatalog = Array.isArray(catalog) ? catalog : [];
306
+ const token = findDirectiveNameTokenAt(safeLine, column);
307
+ if (token === undefined)
308
+ return undefined;
309
+ const entry = findCatalogEntry(safeCatalog, token.name);
310
+ if (entry === undefined)
311
+ return undefined;
312
+ return {
313
+ directiveName: entry.directiveName,
314
+ documentation: componentDocumentation(entry),
315
+ start: token.start,
316
+ end: token.end,
317
+ };
318
+ }
319
+ catch {
320
+ return undefined;
321
+ }
322
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Directive autocompletion (GitHub issue #27, slice 1): the pure line/column
3
+ * parser that decides WHAT kind of thing sits at the cursor — a directive
4
+ * name being typed, an attribute name, or an attribute value — and WHERE
5
+ * the accepted completion's text would replace. No knowledge of the insert
6
+ * catalog or `@markii/stdlib` contracts lives here; this module only reads
7
+ * characters. `./completion.ts` turns what this module finds into the
8
+ * actual `CompletionItem[]`.
9
+ *
10
+ * Every exported function is defensive: an out-of-range or negative
11
+ * `column`, an empty line, an unterminated brace, and an unterminated quote
12
+ * all produce a sensible "nothing here" result rather than throwing.
13
+ */
14
+ import type { ComponentKind } from '../../contracts.js';
15
+ /** Which of the three directive forms the text around the cursor is written as. Matches `ComponentKind`'s three values one-to-one. */
16
+ export type DirectiveForm = ComponentKind;
17
+ /** Attribute names already present in the brace clause, lowercased, minus the partial name currently being typed. */
18
+ export interface AttributeNameParseResult {
19
+ readonly kind: 'attribute-name';
20
+ readonly directiveName: string;
21
+ readonly form: DirectiveForm;
22
+ readonly replaceStart: number;
23
+ readonly replaceEnd: number;
24
+ readonly partial: string;
25
+ readonly presentNames: ReadonlySet<string>;
26
+ }
27
+ export interface AttributeValueParseResult {
28
+ readonly kind: 'attribute-value';
29
+ readonly directiveName: string;
30
+ readonly form: DirectiveForm;
31
+ readonly attributeName: string;
32
+ readonly replaceStart: number;
33
+ readonly replaceEnd: number;
34
+ readonly partial: string;
35
+ /** The quote character the value was opened with, or `undefined` for an unquoted value. */
36
+ readonly quoteChar: '"' | "'" | undefined;
37
+ /** True when a closing quote matching `quoteChar` already sits after the replace range. Always `false` when `quoteChar` is `undefined`. */
38
+ readonly hasClosingQuote: boolean;
39
+ }
40
+ export interface DirectiveNameParseResult {
41
+ readonly kind: 'directive-name';
42
+ readonly form: DirectiveForm;
43
+ /** The colon run exactly as typed (e.g. `':'`, `'::'`, `'::::'`). */
44
+ readonly colonRun: string;
45
+ /** Start of the colon run. */
46
+ readonly replaceStart: number;
47
+ readonly replaceEnd: number;
48
+ /** Start of the NAME token, i.e. `replaceStart + colonRun.length` — after the colon run, where the partial name itself begins. See `CompletionContext.tokenStart`'s doc comment for why this is a separate field from `replaceStart`. */
49
+ readonly tokenStart: number;
50
+ readonly partial: string;
51
+ }
52
+ export type ParsedCompletionContext = AttributeNameParseResult | AttributeValueParseResult | DirectiveNameParseResult | {
53
+ readonly kind: 'none';
54
+ };
55
+ /**
56
+ * Clamps a possibly-hostile column (negative, non-finite, past the end of
57
+ * the line) into `[0, line.length]`.
58
+ */
59
+ export declare function clampColumn(line: string, column: number): number;
60
+ /**
61
+ * The full context-detection algorithm: attribute contexts are checked
62
+ * first, then the directive-name contexts, then `'none'`. `column` is
63
+ * clamped defensively; this function never throws.
64
+ */
65
+ export declare function parseCompletionContext(line: string, column: number): ParsedCompletionContext;
66
+ /** A directive name token found anywhere on the line, for `hoverAt`. */
67
+ export interface DirectiveNameToken {
68
+ readonly name: string;
69
+ readonly form: DirectiveForm;
70
+ readonly start: number;
71
+ readonly end: number;
72
+ }
73
+ /**
74
+ * Finds the directive-name token whose range contains or abuts `column`,
75
+ * anywhere on the line (not only at the end of the cursor's prefix, since
76
+ * hover targets an already-written directive). `undefined` when no valid
77
+ * opener's name range covers `column`.
78
+ */
79
+ export declare function findDirectiveNameTokenAt(line: string, column: number): DirectiveNameToken | undefined;