@falsefalse/prettier-plugin-handlebars 0.0.1

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,222 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.parseCall = parseCall;
37
+ const template_format_core_1 = require("template-format-core");
38
+ const errors_1 = require("./errors");
39
+ const whitespace = __importStar(require("./whitespace"));
40
+ const quoteCharacters = new Set(['"', "'", '`']);
41
+ const numberPattern = /^-?(?:\d+\.?\d*|\.\d+)$/u;
42
+ function literalTypeOf(source) {
43
+ switch (source) {
44
+ case 'true':
45
+ case 'false':
46
+ return 'BooleanLiteral';
47
+ case 'null':
48
+ return 'NullLiteral';
49
+ case 'undefined':
50
+ return 'UndefinedLiteral';
51
+ default:
52
+ return numberPattern.test(source) ? 'NumberLiteral' : null;
53
+ }
54
+ }
55
+ /**
56
+ * Recursive-descent reader over one call's source, e.g. `t 'a.b' n=(concat x y)`.
57
+ *
58
+ * Total by construction: anything it cannot classify becomes a PathExpression holding the raw
59
+ * text, because a formatter has to keep working on templates that are mid-edit.
60
+ */
61
+ class CallReader {
62
+ constructor(source, offset) {
63
+ this.source = source;
64
+ this.offset = offset;
65
+ this.index = 0;
66
+ }
67
+ get done() {
68
+ return this.index >= this.source.length;
69
+ }
70
+ peek(at = 0) {
71
+ return this.source[this.index + at] ?? '';
72
+ }
73
+ skipWhitespace() {
74
+ while (!this.done && whitespace.handlebars.test(this.peek())) {
75
+ this.index += 1;
76
+ }
77
+ }
78
+ span(start, end) {
79
+ return [this.offset + start, this.offset + end];
80
+ }
81
+ /** `as |a b|` closes the parameter list; the names themselves have nothing to break. */
82
+ readBlockParams() {
83
+ const rest = this.source.slice(this.index);
84
+ const match = /^as\s+\|([^|]*)\|/u.exec(rest);
85
+ if (!match) {
86
+ return null;
87
+ }
88
+ this.index += match[0].length;
89
+ return match[1].trim().split(whitespace.handlebarsRun).filter(Boolean);
90
+ }
91
+ skipQuoted() {
92
+ const quote = this.peek();
93
+ this.index += 1;
94
+ while (!this.done) {
95
+ const char = this.peek();
96
+ if (char === '\\') {
97
+ this.index += 2;
98
+ continue;
99
+ }
100
+ this.index += 1;
101
+ if (char === quote) {
102
+ break;
103
+ }
104
+ }
105
+ }
106
+ /** A bare run stops at whitespace, a closing paren, or the `=` of a hash pair. */
107
+ skipBare() {
108
+ let brackets = 0;
109
+ while (!this.done) {
110
+ const char = this.peek();
111
+ if (char === '[')
112
+ brackets += 1;
113
+ else if (char === ']')
114
+ brackets = Math.max(brackets - 1, 0);
115
+ else if (brackets === 0 && (whitespace.handlebars.test(char) || char === ')' || char === '='))
116
+ break;
117
+ this.index += 1;
118
+ }
119
+ }
120
+ /** A leaf is its own source text and the span it came from; only the label differs. */
121
+ leaf(type, start) {
122
+ const node = { type, source: this.source.slice(start, this.index) };
123
+ return (0, template_format_core_1.withRange)(node, ...this.span(start, this.index));
124
+ }
125
+ readSubExpression() {
126
+ const start = this.index;
127
+ this.index += 1;
128
+ const inner = this.readCall(true);
129
+ /* Printing the parts back out would invent the `)` the author did not write, turning a
130
+ * template Handlebars rejects into one it accepts - the opposite of what this branch does
131
+ * everywhere else. */
132
+ if (this.peek() !== ')') {
133
+ throw new errors_1.TemplateSyntaxError("unterminated subexpression: expected ')'", ...this.span(start, this.index));
134
+ }
135
+ this.index += 1;
136
+ const node = {
137
+ type: 'SubExpression',
138
+ source: this.source.slice(start, this.index),
139
+ path: inner.path,
140
+ params: inner.params,
141
+ hash: inner.hash,
142
+ };
143
+ return (0, template_format_core_1.withRange)(node, ...this.span(start, this.index));
144
+ }
145
+ readValue() {
146
+ if (this.peek() === '(') {
147
+ return this.readSubExpression();
148
+ }
149
+ const start = this.index;
150
+ if (quoteCharacters.has(this.peek())) {
151
+ this.skipQuoted();
152
+ return this.leaf('StringLiteral', start);
153
+ }
154
+ this.skipBare();
155
+ /* An empty read would spin forever; consume the character as a path instead. */
156
+ if (this.index === start) {
157
+ this.index += 1;
158
+ }
159
+ return this.leaf(literalTypeOf(this.source.slice(start, this.index)) ?? 'PathExpression', start);
160
+ }
161
+ /** A head must be callable, so a stray literal is reread as a path rather than rejected. */
162
+ readHead() {
163
+ const value = this.readValue();
164
+ if (value.type === 'SubExpression' || value.type === 'PathExpression') {
165
+ return value;
166
+ }
167
+ const node = { type: 'PathExpression', source: value.source };
168
+ return value.range ? (0, template_format_core_1.withRange)(node, ...value.range) : node;
169
+ }
170
+ readCall(nested = false) {
171
+ this.skipWhitespace();
172
+ const path = this.done || this.peek() === ')' ? this.emptyPath() : this.readHead();
173
+ const params = [];
174
+ const hash = [];
175
+ let blockParams;
176
+ for (;;) {
177
+ this.skipWhitespace();
178
+ if (this.done || (nested && this.peek() === ')')) {
179
+ break;
180
+ }
181
+ const names = this.readBlockParams();
182
+ if (names) {
183
+ blockParams = names;
184
+ continue;
185
+ }
186
+ const start = this.index;
187
+ const value = this.readValue();
188
+ /* Handlebars allows space on either side of the `=`, so look past it and rewind when the
189
+ * next token turns out to be a plain param rather than a hash value. */
190
+ const afterValue = this.index;
191
+ this.skipWhitespace();
192
+ if (this.peek() === '=' && value.type === 'PathExpression') {
193
+ this.index += 1;
194
+ this.skipWhitespace();
195
+ const pairValue = this.readValue();
196
+ hash.push((0, template_format_core_1.withRange)({ key: value.source, value: pairValue }, ...this.span(start, this.index)));
197
+ continue;
198
+ }
199
+ this.index = afterValue;
200
+ /* Handlebars rejects a positional param after a hash pair, and the printer prints params
201
+ * first regardless - so accepting this would silently re-order the author's arguments. */
202
+ if (hash.length > 0) {
203
+ throw new errors_1.TemplateSyntaxError(`unexpected ${value.source} after a hash pair: positional params come first`, ...this.span(start, this.index));
204
+ }
205
+ params.push(value);
206
+ }
207
+ return blockParams ? { path, params, hash, blockParams } : { path, params, hash };
208
+ }
209
+ emptyPath() {
210
+ const node = { type: 'PathExpression', source: '' };
211
+ return (0, template_format_core_1.withRange)(node, ...this.span(this.index, this.index));
212
+ }
213
+ }
214
+ /**
215
+ * Parses one call's source into structured parts whose ranges are absolute in the template.
216
+ *
217
+ * `offset` is where `source` begins in the template, so a subexpression buried in a hash value
218
+ * can still be located exactly.
219
+ */
220
+ function parseCall(source, offset = 0) {
221
+ return new CallReader(source, offset).readCall();
222
+ }
@@ -0,0 +1,4 @@
1
+ import { Program } from './types';
2
+ import { locEnd, locStart } from 'template-format-core';
3
+ export { locEnd, locStart };
4
+ export declare function parse(text: string): Program;