@falsefalse/prettier-plugin-handlebars 0.0.2 → 0.0.3
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 +2 -1
- package/README.md +19 -15
- package/dist/{errors.d.ts → core/errors.d.ts} +7 -0
- package/dist/{errors.js → core/errors.js} +10 -0
- package/dist/core/html.d.ts +2 -0
- package/dist/core/html.js +32 -0
- package/dist/core/source.d.ts +11 -0
- package/dist/core/source.js +27 -0
- package/dist/{whitespace.d.ts → core/whitespace.d.ts} +5 -0
- package/dist/{whitespace.js → core/whitespace.js} +18 -0
- package/dist/dialects/handlebars/tokens.d.ts +23 -6
- package/dist/dialects/handlebars/tokens.js +15 -8
- package/dist/expression.js +8 -8
- package/dist/parse/lex.d.ts +86 -0
- package/dist/parse/lex.js +274 -0
- package/dist/parse/lookahead.d.ts +35 -0
- package/dist/parse/lookahead.js +305 -0
- package/dist/parse/nodes.d.ts +38 -0
- package/dist/parse/nodes.js +161 -0
- package/dist/parser.d.ts +0 -2
- package/dist/parser.js +271 -931
- package/dist/plugin.d.ts +2 -1
- package/dist/plugin.js +3 -2
- package/dist/printer.d.ts +1 -6
- package/dist/printer.js +72 -35
- package/dist/types.d.ts +2 -2
- package/docs/EDITOR_SETUP.md +0 -1
- package/package.json +3 -6
- /package/dist/{scan.d.ts → core/scan.d.ts} +0 -0
- /package/dist/{scan.js → core/scan.js} +0 -0
package/LICENSE
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026
|
|
3
|
+
Copyright (c) 2026 Illia Furman
|
|
4
|
+
Portions copyright (c) 2026 Poliklot
|
|
4
5
|
|
|
5
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
7
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
A Prettier plugin for classic Handlebars templates with mixed HTML markup.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
It is **opinionated**: it exposes no options of its own, only Prettier's core `printWidth`,
|
|
6
|
+
`tabWidth` and `useTabs`. Everything else is a decision the formatter has already
|
|
7
|
+
made.
|
|
8
8
|
|
|
9
9
|
## The rule everything follows
|
|
10
10
|
|
|
@@ -27,7 +27,7 @@ Two consequences worth stating plainly:
|
|
|
27
27
|
## Install
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
npm install --save-dev prettier
|
|
30
|
+
npm install --save-dev prettier @falsefalse/prettier-plugin-handlebars
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
```js
|
|
@@ -106,8 +106,21 @@ Or fence the region off entirely. Nothing inside is parsed, so nothing inside ca
|
|
|
106
106
|
|
|
107
107
|
## Options
|
|
108
108
|
|
|
109
|
-
None. `printWidth`, `tabWidth
|
|
110
|
-
|
|
109
|
+
None. `printWidth`, `tabWidth` and `useTabs` are read from Prettier's core config; this plugin
|
|
110
|
+
adds nothing. `singleQuote` is **ignored** - quoting is part of the opinion.
|
|
111
|
+
|
|
112
|
+
Quotes are `"` around an HTML attribute value and `'` around a string literal in a mustache:
|
|
113
|
+
|
|
114
|
+
```hbs
|
|
115
|
+
<div class="card" title="{{t 'card.title'}}">{{t 'card.body' count=n}}</div>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The two are complementary, so a literal inside an attribute already wears the quote the attribute
|
|
119
|
+
did not and neither has to give way. Both still yield to the quote they sit inside and to
|
|
120
|
+
whichever one needs no escaping, so `{{t "it's"}}` keeps its double quote. A literal inside a
|
|
121
|
+
block in attribute position, `<img {{#if m}}alt="{{t 'k'}}"{{/if}}>`, keeps the quote the author
|
|
122
|
+
gave it: there the enclosing quote is only half of a text node, so no reader downstream can tell
|
|
123
|
+
what it is.
|
|
111
124
|
|
|
112
125
|
## Development
|
|
113
126
|
|
|
@@ -133,15 +146,6 @@ correctness; they cannot see bad taste, which is the failure mode that actually
|
|
|
133
146
|
[docs/REWRITE-PLAN.md](./docs/REWRITE-PLAN.md) is the design record — why the printer looks like
|
|
134
147
|
this, and what the previous one got wrong.
|
|
135
148
|
|
|
136
|
-
## Prior work
|
|
137
|
-
|
|
138
|
-
This plugin began as a fork of
|
|
139
|
-
[Poliklot/prettier-plugin-handlebars](https://github.com/Poliklot/prettier-plugin-handlebars),
|
|
140
|
-
published as `@poliklot/prettier-plugin-handlebars` and MIT-licensed, © Poliklot. The parser and
|
|
141
|
-
printer have since been rewritten — [docs/REWRITE-PLAN.md](./docs/REWRITE-PLAN.md) is that record
|
|
142
|
-
— and the package now ships under its own name, but the shape of the project, a Handlebars-aware
|
|
143
|
-
`.hbs` formatter with no options of its own, starts there.
|
|
144
|
-
|
|
145
149
|
## Docs
|
|
146
150
|
|
|
147
151
|
- [Editor setup](./docs/EDITOR_SETUP.md)
|
|
@@ -21,4 +21,11 @@ export declare class TemplateSyntaxError extends SyntaxError {
|
|
|
21
21
|
/** Offsets are all the parser knows; line and column need the whole text. */
|
|
22
22
|
locate(text: string): this;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Every malformed construct ends here. A formatter that guesses at a missing delimiter prints
|
|
26
|
+
* markup the author did not write; one that passes a mismatched tag through leaves the rest of
|
|
27
|
+
* the file unformatted with nothing to show for it. Refusing is the only honest option, and the
|
|
28
|
+
* offsets let an editor put the cursor on the offending place.
|
|
29
|
+
*/
|
|
30
|
+
export declare function fail(message: string, start: number, end: number): never;
|
|
24
31
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TemplateSyntaxError = void 0;
|
|
4
|
+
exports.fail = fail;
|
|
4
5
|
function positionAt(text, offset) {
|
|
5
6
|
const upTo = text.slice(0, Math.max(0, Math.min(offset, text.length)));
|
|
6
7
|
return { line: upTo.split('\n').length, column: upTo.length - (upTo.lastIndexOf('\n') + 1) + 1 };
|
|
@@ -30,3 +31,12 @@ class TemplateSyntaxError extends SyntaxError {
|
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
exports.TemplateSyntaxError = TemplateSyntaxError;
|
|
34
|
+
/**
|
|
35
|
+
* Every malformed construct ends here. A formatter that guesses at a missing delimiter prints
|
|
36
|
+
* markup the author did not write; one that passes a mismatched tag through leaves the rest of
|
|
37
|
+
* the file unformatted with nothing to show for it. Refusing is the only honest option, and the
|
|
38
|
+
* offsets let an editor put the cursor on the offending place.
|
|
39
|
+
*/
|
|
40
|
+
function fail(message, start, end) {
|
|
41
|
+
throw new TemplateSyntaxError(message, start, end);
|
|
42
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isVoidElement = isVoidElement;
|
|
4
|
+
exports.isRawTextElement = isRawTextElement;
|
|
5
|
+
/** Elements with no closing tag. Writing one is an error, not a shorthand. */
|
|
6
|
+
const voidElements = new Set([
|
|
7
|
+
'area',
|
|
8
|
+
'base',
|
|
9
|
+
'br',
|
|
10
|
+
'col',
|
|
11
|
+
'embed',
|
|
12
|
+
'hr',
|
|
13
|
+
'img',
|
|
14
|
+
'input',
|
|
15
|
+
'keygen',
|
|
16
|
+
'link',
|
|
17
|
+
'meta',
|
|
18
|
+
'param',
|
|
19
|
+
'source',
|
|
20
|
+
'track',
|
|
21
|
+
'wbr',
|
|
22
|
+
]);
|
|
23
|
+
/** Elements whose content is text, not markup: a `<` inside one opens nothing. */
|
|
24
|
+
const rawTextElements = new Set(['script', 'style', 'textarea', 'pre']);
|
|
25
|
+
/* Both sets are keyed lowercase and a tag name is not: `<BR>` is a `br`. Asking through these
|
|
26
|
+
* rather than reaching for the set is what keeps the fold from being forgotten at a call site. */
|
|
27
|
+
function isVoidElement(tag) {
|
|
28
|
+
return voidElements.has(tag.toLowerCase());
|
|
29
|
+
}
|
|
30
|
+
function isRawTextElement(tag) {
|
|
31
|
+
return rawTextElements.has(tag.toLowerCase());
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Where a node came from. Optional: a node built rather than read has no source to point at. */
|
|
2
|
+
export interface SourceRange {
|
|
3
|
+
range?: [number, number];
|
|
4
|
+
}
|
|
5
|
+
/** A BOM and a CRLF are not content. Every offset the parser records counts the result. */
|
|
6
|
+
export declare function normalizeInput(text: string): string;
|
|
7
|
+
export declare function locStart(node: SourceRange): number;
|
|
8
|
+
export declare function locEnd(node: SourceRange): number;
|
|
9
|
+
export declare function withRange<T extends object>(node: T, start: number, end: number): T;
|
|
10
|
+
/** For a node whose span is only sometimes known - an attribute value outside a template. */
|
|
11
|
+
export declare function withOptionalRange<T extends object>(node: T, start?: number, end?: number): T;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeInput = normalizeInput;
|
|
4
|
+
exports.locStart = locStart;
|
|
5
|
+
exports.locEnd = locEnd;
|
|
6
|
+
exports.withRange = withRange;
|
|
7
|
+
exports.withOptionalRange = withOptionalRange;
|
|
8
|
+
/** A BOM and a CRLF are not content. Every offset the parser records counts the result. */
|
|
9
|
+
function normalizeInput(text) {
|
|
10
|
+
return text.replace(/^\uFEFF/u, '').replace(/\r\n?/gu, '\n');
|
|
11
|
+
}
|
|
12
|
+
function locStart(node) {
|
|
13
|
+
return node.range?.[0] ?? 0;
|
|
14
|
+
}
|
|
15
|
+
function locEnd(node) {
|
|
16
|
+
return node.range?.[1] ?? 0;
|
|
17
|
+
}
|
|
18
|
+
/* Non-enumerable, so a range never reaches a snapshot, a `JSON.stringify` or a structural
|
|
19
|
+
* comparison between an expected node and a parsed one. */
|
|
20
|
+
function withRange(node, start, end) {
|
|
21
|
+
Object.defineProperty(node, 'range', { value: [start, end], enumerable: false, configurable: true });
|
|
22
|
+
return node;
|
|
23
|
+
}
|
|
24
|
+
/** For a node whose span is only sometimes known - an attribute value outside a template. */
|
|
25
|
+
function withOptionalRange(node, start, end) {
|
|
26
|
+
return typeof start === 'number' && typeof end === 'number' ? withRange(node, start, end) : node;
|
|
27
|
+
}
|
|
@@ -26,3 +26,8 @@ export declare const htmlRunGlobal: RegExp;
|
|
|
26
26
|
export declare const handlebars: RegExp;
|
|
27
27
|
/** `/\s+/u` - splitting a mustache's inner text into words. */
|
|
28
28
|
export declare const handlebarsRun: RegExp;
|
|
29
|
+
/**
|
|
30
|
+
* Every line shifted left by the smallest indent any non-blank line carries, trailing spaces
|
|
31
|
+
* and tabs dropped. Keeps a block's relative shape while letting the printer own its column.
|
|
32
|
+
*/
|
|
33
|
+
export declare function stripCommonIndent(lines: string[]): string[];
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.handlebarsRun = exports.handlebars = exports.htmlRunGlobal = exports.htmlRun = exports.html = exports.htmlCharacters = void 0;
|
|
11
|
+
exports.stripCommonIndent = stripCommonIndent;
|
|
11
12
|
/**
|
|
12
13
|
* HTML's set. Not `\s`, which also matches U+00A0 - a non-breaking space is content, so
|
|
13
14
|
* `<div title=a b>` is one attribute holding one and `<div a b>` is one attribute named `a b`.
|
|
@@ -29,3 +30,20 @@ exports.htmlRunGlobal = new RegExp(`[${exports.htmlCharacters}]+`, 'gu');
|
|
|
29
30
|
exports.handlebars = /\s/u;
|
|
30
31
|
/** `/\s+/u` - splitting a mustache's inner text into words. */
|
|
31
32
|
exports.handlebarsRun = /\s+/u;
|
|
33
|
+
/** How many spaces or tabs a line opens with. Not `trimStart`, which also eats a U+00A0. */
|
|
34
|
+
function indentOf(line) {
|
|
35
|
+
let at = 0;
|
|
36
|
+
while (line[at] === ' ' || line[at] === '\t')
|
|
37
|
+
at += 1;
|
|
38
|
+
return at;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Every line shifted left by the smallest indent any non-blank line carries, trailing spaces
|
|
42
|
+
* and tabs dropped. Keeps a block's relative shape while letting the printer own its column.
|
|
43
|
+
*/
|
|
44
|
+
function stripCommonIndent(lines) {
|
|
45
|
+
const common = lines
|
|
46
|
+
.filter((line) => line.trim() !== '')
|
|
47
|
+
.reduce((least, line) => Math.min(least, indentOf(line)), Infinity);
|
|
48
|
+
return lines.map((line) => line.trim() === '' ? '' : line.slice(Math.min(common, indentOf(line))).replace(/[ \t]+$/u, ''));
|
|
49
|
+
}
|
|
@@ -1,5 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
type TokenKind = 'blockStart' | 'blockEnd' | 'partial' | 'comment' | 'mustache' | 'else';
|
|
2
|
+
/** How a block opens, which is also how its closer and its `{{else}}` are spelled. */
|
|
3
|
+
type BlockPrefix = '#' | '#>' | '#*' | '^' | '<' | '$';
|
|
4
|
+
/** A form the printer reproduces but the parser treats as its plain counterpart. */
|
|
5
|
+
type SpecialForm = 'blockPartial' | 'decoratorBlock' | 'decorator' | 'elseIf' | 'inverseBlock' | 'parent' | 'mustacheBlock';
|
|
6
|
+
export interface HandlebarsToken {
|
|
7
|
+
kind: TokenKind;
|
|
8
|
+
/** The inner text, whitespace control and the leading sigil stripped. */
|
|
9
|
+
content: string;
|
|
10
|
+
/** The inner text exactly as written, delimiters aside. */
|
|
11
|
+
rawContent: string;
|
|
12
|
+
start: number;
|
|
13
|
+
end: number;
|
|
14
|
+
triple: boolean;
|
|
15
|
+
/** The path a block opens or closes on. Absent on everything that opens nothing. */
|
|
16
|
+
name?: string;
|
|
17
|
+
trimOpen: boolean;
|
|
18
|
+
trimClose: boolean;
|
|
19
|
+
specialForm?: SpecialForm;
|
|
3
20
|
/**
|
|
4
21
|
* Whether the tokenizer found a closing delimiter, rather than running to the end of the
|
|
5
22
|
* input. Recorded by the one place that knows: re-deriving it by string-matching the token's
|
|
@@ -45,10 +62,10 @@ export declare function handlebarsRawBlockName(text: string, position: number, o
|
|
|
45
62
|
*/
|
|
46
63
|
export declare function handlebarsRawBlockCloser(name: string): string;
|
|
47
64
|
declare function consumeHandlebarsRawBlock(text: string, position: number): number | null;
|
|
48
|
-
declare function getHandlebarsBlockExpression(token:
|
|
49
|
-
declare function getHandlebarsBlockPrefix(token:
|
|
50
|
-
declare function getPrintedHandlebarsBlockPrefix(prefix:
|
|
65
|
+
declare function getHandlebarsBlockExpression(token: HandlebarsToken): string;
|
|
66
|
+
declare function getHandlebarsBlockPrefix(token: HandlebarsToken): BlockPrefix;
|
|
67
|
+
declare function getPrintedHandlebarsBlockPrefix(prefix: BlockPrefix): string;
|
|
51
68
|
declare function getHandlebarsElseKeyword(): string;
|
|
52
69
|
declare function getHandlebarsBlockClosePrefix(path: string): string;
|
|
53
|
-
declare function shouldPreserveHandlebarsTokenVerbatim(token:
|
|
70
|
+
declare function shouldPreserveHandlebarsTokenVerbatim(token: HandlebarsToken): boolean;
|
|
54
71
|
export {};
|
|
@@ -37,12 +37,11 @@ exports.handlebarsDialect = void 0;
|
|
|
37
37
|
exports.isHandlebarsBlockComment = isHandlebarsBlockComment;
|
|
38
38
|
exports.handlebarsRawBlockName = handlebarsRawBlockName;
|
|
39
39
|
exports.handlebarsRawBlockCloser = handlebarsRawBlockCloser;
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* parser and printer
|
|
45
|
-
* hand - `getLineCommentTag` and `printComment` disagree about the same thing. */
|
|
40
|
+
const scan_1 = require("../../core/scan");
|
|
41
|
+
const whitespace = __importStar(require("../../core/whitespace"));
|
|
42
|
+
/* Deliberately untyped. An interface here would fix a member for every piece of Handlebars
|
|
43
|
+
* syntax, each a second copy to keep in step with the printer by hand; the inferred shape is
|
|
44
|
+
* exactly what the parser and printer ask for. */
|
|
46
45
|
exports.handlebarsDialect = {
|
|
47
46
|
openDelimiter: '{{',
|
|
48
47
|
parseToken: parseHandlebarsToken,
|
|
@@ -108,7 +107,6 @@ function parseHandlebarsToken(text, position) {
|
|
|
108
107
|
const inner = rawInner.replace(/^~/, '').replace(/~$/, '').trim();
|
|
109
108
|
const baseToken = {
|
|
110
109
|
rawContent,
|
|
111
|
-
rawInner,
|
|
112
110
|
start: position,
|
|
113
111
|
end,
|
|
114
112
|
triple,
|
|
@@ -186,10 +184,19 @@ function findHandlebarsBlockCommentClose(text, position, close) {
|
|
|
186
184
|
}
|
|
187
185
|
return plain < 0 ? null : { index: plain, end: plain + close.length + 2, trimClose: false };
|
|
188
186
|
}
|
|
187
|
+
/* A quote opens a string only where a value can start. Mid-token - `it's` in `{{t it's}}` -
|
|
188
|
+
* it is an apostrophe, and treating it as an opening quote runs the scan past the real `}}`. */
|
|
189
|
+
function opensQuote(text, index, expressionStart) {
|
|
190
|
+
if (index <= expressionStart) {
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
const previous = text[index - 1];
|
|
194
|
+
return !previous || whitespace.handlebars.test(previous) || /[([{=,:~|]/u.test(previous);
|
|
195
|
+
}
|
|
189
196
|
function findHandlebarsClose(text, position, closeDelimiter) {
|
|
190
197
|
return (0, scan_1.scanPastQuotes)(text, position, {
|
|
191
198
|
stopsAt: (index) => text.startsWith(closeDelimiter, index),
|
|
192
|
-
opensQuote: (index) => (
|
|
199
|
+
opensQuote: (index) => opensQuote(text, index, position),
|
|
193
200
|
});
|
|
194
201
|
}
|
|
195
202
|
function isEscapedHandlebarsOpen(text, position) {
|
package/dist/expression.js
CHANGED
|
@@ -34,9 +34,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.parseCall = parseCall;
|
|
37
|
-
const
|
|
38
|
-
const errors_1 = require("./errors");
|
|
39
|
-
const whitespace = __importStar(require("./whitespace"));
|
|
37
|
+
const source_1 = require("./core/source");
|
|
38
|
+
const errors_1 = require("./core/errors");
|
|
39
|
+
const whitespace = __importStar(require("./core/whitespace"));
|
|
40
40
|
const quoteCharacters = new Set(['"', "'", '`']);
|
|
41
41
|
const numberPattern = /^-?(?:\d+\.?\d*|\.\d+)$/u;
|
|
42
42
|
function literalTypeOf(source) {
|
|
@@ -120,7 +120,7 @@ class CallReader {
|
|
|
120
120
|
/** A leaf is its own source text and the span it came from; only the label differs. */
|
|
121
121
|
leaf(type, start) {
|
|
122
122
|
const node = { type, source: this.source.slice(start, this.index) };
|
|
123
|
-
return (0,
|
|
123
|
+
return (0, source_1.withRange)(node, ...this.span(start, this.index));
|
|
124
124
|
}
|
|
125
125
|
readSubExpression() {
|
|
126
126
|
const start = this.index;
|
|
@@ -140,7 +140,7 @@ class CallReader {
|
|
|
140
140
|
params: inner.params,
|
|
141
141
|
hash: inner.hash,
|
|
142
142
|
};
|
|
143
|
-
return (0,
|
|
143
|
+
return (0, source_1.withRange)(node, ...this.span(start, this.index));
|
|
144
144
|
}
|
|
145
145
|
readValue() {
|
|
146
146
|
if (this.peek() === '(') {
|
|
@@ -165,7 +165,7 @@ class CallReader {
|
|
|
165
165
|
return value;
|
|
166
166
|
}
|
|
167
167
|
const node = { type: 'PathExpression', source: value.source };
|
|
168
|
-
return value.range ? (0,
|
|
168
|
+
return value.range ? (0, source_1.withRange)(node, ...value.range) : node;
|
|
169
169
|
}
|
|
170
170
|
readCall(nested = false) {
|
|
171
171
|
this.skipWhitespace();
|
|
@@ -193,7 +193,7 @@ class CallReader {
|
|
|
193
193
|
this.index += 1;
|
|
194
194
|
this.skipWhitespace();
|
|
195
195
|
const pairValue = this.readValue();
|
|
196
|
-
hash.push((0,
|
|
196
|
+
hash.push((0, source_1.withRange)({ key: value.source, value: pairValue }, ...this.span(start, this.index)));
|
|
197
197
|
continue;
|
|
198
198
|
}
|
|
199
199
|
this.index = afterValue;
|
|
@@ -208,7 +208,7 @@ class CallReader {
|
|
|
208
208
|
}
|
|
209
209
|
emptyPath() {
|
|
210
210
|
const node = { type: 'PathExpression', source: '' };
|
|
211
|
-
return (0,
|
|
211
|
+
return (0, source_1.withRange)(node, ...this.span(this.index, this.index));
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
214
|
/**
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ElementAttribute } from '../types';
|
|
2
|
+
export declare const openDelimiter: string, isEscapedOpen: (text: string, position: number) => boolean, parseMustacheToken: (text: string, position: number) => import("../dialects/handlebars/tokens").HandlebarsToken;
|
|
3
|
+
export declare const leadingWhitespace: RegExp;
|
|
4
|
+
/** What an attribute name is made of: anything but whitespace and the characters that end one. */
|
|
5
|
+
export declare const attributeNameCharacter: RegExp;
|
|
6
|
+
/** What ends a tag name. HTML's tag-name state leaves on whitespace, `/` or `>`, and nothing else. */
|
|
7
|
+
export declare const tagNameTerminator: RegExp;
|
|
8
|
+
export declare function startsTemplateTag(text: string, position: number): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Where a tag ends, what it is called and whether it closed - without building a single node and
|
|
11
|
+
* without rejecting anything.
|
|
12
|
+
*
|
|
13
|
+
* Lookahead has to be total: callers scan regions they may go on to skip, including a
|
|
14
|
+
* `{{! prettier-ignore }}` body, so a `parseTag` here let the directive reject the very file it
|
|
15
|
+
* was written to protect. `terminated` is false when the tag ran to EOF, which is also how an
|
|
16
|
+
* unterminated attribute value shows up.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Past the mustache at `position`. A token reporting an end at or before where it started would
|
|
20
|
+
* leave the caller's loop standing still, so the opening delimiter is the smallest step taken.
|
|
21
|
+
*/
|
|
22
|
+
export declare function skipTemplateTag(text: string, position: number): number;
|
|
23
|
+
export declare function scanTag(text: string, position: number): {
|
|
24
|
+
kind: 'open' | 'selfClosing' | 'close';
|
|
25
|
+
tag: string;
|
|
26
|
+
end: number;
|
|
27
|
+
terminated: boolean;
|
|
28
|
+
};
|
|
29
|
+
export type ParsedTag = {
|
|
30
|
+
kind: 'open';
|
|
31
|
+
tag: string;
|
|
32
|
+
attributes: ElementAttribute[];
|
|
33
|
+
attributesRange: [number, number];
|
|
34
|
+
end: number;
|
|
35
|
+
terminated: boolean;
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'selfClosing';
|
|
38
|
+
tag: string;
|
|
39
|
+
attributes: ElementAttribute[];
|
|
40
|
+
attributesRange: [number, number];
|
|
41
|
+
end: number;
|
|
42
|
+
terminated: boolean;
|
|
43
|
+
} | {
|
|
44
|
+
kind: 'close';
|
|
45
|
+
tag: string;
|
|
46
|
+
source: string;
|
|
47
|
+
end: number;
|
|
48
|
+
terminated: boolean;
|
|
49
|
+
};
|
|
50
|
+
export declare function sameTag(one: string, other: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Whether a close tag for exactly `tag` starts here.
|
|
53
|
+
*
|
|
54
|
+
* The name has to end where `tag` does. On a prefix comparison `</bdi>` would close a `<b>`,
|
|
55
|
+
* deleting `di` from the source and pointing any error at the next, well-formed close tag.
|
|
56
|
+
*/
|
|
57
|
+
export declare function startsCloseTag(text: string, position: number, tag: string): boolean;
|
|
58
|
+
export declare function readCloseTagSource(text: string, position: number, closeIdx: number): string;
|
|
59
|
+
export declare function trimTrailingWhitespace(text: string, from: number): number;
|
|
60
|
+
export declare function isTagStart(text: string, position: number): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* An attribute value's extent and its text, whichever way it was written. The quotes are not
|
|
63
|
+
* part of the value, so `start` is inside them - getting that `+ 1` wrong shifts every range a
|
|
64
|
+
* mustache inside the value reports.
|
|
65
|
+
*/
|
|
66
|
+
export declare function readAttributeValue(text: string, position: number): {
|
|
67
|
+
raw: string;
|
|
68
|
+
start: number;
|
|
69
|
+
end: number;
|
|
70
|
+
};
|
|
71
|
+
export declare function skipWhitespace(text: string, position: number): number;
|
|
72
|
+
/**
|
|
73
|
+
* HTML's attribute-name state ends at whitespace, `/`, `>` or `=`, and nowhere else.
|
|
74
|
+
*
|
|
75
|
+
* Matching a tag-name charset instead stepped over one character and carried on: `@click` came
|
|
76
|
+
* back as `click` and `(click)="go()"` as two boolean attributes, value gone, silently.
|
|
77
|
+
*/
|
|
78
|
+
export declare function readAttributeName(text: string, position: number): {
|
|
79
|
+
value: string;
|
|
80
|
+
next: number;
|
|
81
|
+
};
|
|
82
|
+
export declare function readName(text: string, position: number): {
|
|
83
|
+
value: string;
|
|
84
|
+
next: number;
|
|
85
|
+
};
|
|
86
|
+
export declare function consumeTagLikeChunk(text: string, position: number): number;
|