ember-estree 0.4.3 → 0.5.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/README.md +103 -0
- package/package.json +1 -1
- package/src/index.d.ts +15 -11
- package/src/parse.js +69 -96
- package/src/transforms.js +33 -40
package/README.md
CHANGED
|
@@ -57,6 +57,109 @@ print({
|
|
|
57
57
|
// => "<template>Hello</template>"
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
## Options
|
|
61
|
+
|
|
62
|
+
Both `toTree` and `parse` accept an options object as their second argument.
|
|
63
|
+
|
|
64
|
+
All options are optional.
|
|
65
|
+
|
|
66
|
+
| Option | Type | Description |
|
|
67
|
+
| -------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
68
|
+
| `filePath` | `string` | Used for language detection. |
|
|
69
|
+
| `templateOnly` | `boolean` | Parse the source as a raw Glimmer template. Use for `.hbs` files. |
|
|
70
|
+
| `parser` | `(placeholderJS: string) => { ast, ... }` | Use a custom JS/TS parser instead of the default oxc-parser. See [Custom parser](#custom-parser). |
|
|
71
|
+
| `visitors` | `VisitorMap` <br /> or `(outerAst) => VisitorMap` | Callbacks fired on every node during traversal — JS/TS and Glimmer — in a single pass. See [Visitors](#visitors). |
|
|
72
|
+
|
|
73
|
+
Handler signature is `(node, path) => void`, where `path = { node, parent, parentPath }` — a linked list walking back to the root.
|
|
74
|
+
|
|
75
|
+
### Custom parser
|
|
76
|
+
|
|
77
|
+
Pass any JS/TS parser that returns an ESTree-compatible AST. ember-estree handles template splicing and Glimmer traversal on top of it.
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
import { parseSync } from "oxc-parser";
|
|
81
|
+
import { toTree } from "ember-estree";
|
|
82
|
+
|
|
83
|
+
const result = toTree(source, {
|
|
84
|
+
parser: (js) => ({
|
|
85
|
+
ast: parseSync("input.ts", js).program,
|
|
86
|
+
visitorKeys: {
|
|
87
|
+
/* ...parser's visitor keys... */
|
|
88
|
+
},
|
|
89
|
+
}),
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The parser receives a placeholder-JS string (templates replaced with backtick expressions of equal length) and must return at least `{ ast }`. Additional fields like `scopeManager`, `visitorKeys`, or `services` are preserved on the returned result.
|
|
94
|
+
|
|
95
|
+
### Visitors
|
|
96
|
+
|
|
97
|
+
Pass `visitors` to observe or rewrite the tree in a single traversal. Handlers fire on both outer JS/TS nodes and spliced Glimmer subtrees, and a single node is never dispatched twice — safe to relocate nodes mid-walk.
|
|
98
|
+
|
|
99
|
+
The pseudo-type `GlimmerBlockParams` fires on any node that carries a `blockParams` array.
|
|
100
|
+
|
|
101
|
+
**Plain-object form** — use when you only need the type → handler map:
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
import { toTree } from "ember-estree";
|
|
105
|
+
|
|
106
|
+
const identifiers = [];
|
|
107
|
+
toTree(source, {
|
|
108
|
+
visitors: {
|
|
109
|
+
Identifier: (node) => identifiers.push(node.name),
|
|
110
|
+
GlimmerPathExpression: (node) => identifiers.push(node.original),
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Factory form** — use when you need the outer JS/TS AST up front (for example, to attach state to it before the walk):
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
import { toTree, print } from "ember-estree";
|
|
119
|
+
|
|
120
|
+
const ast = toTree(`const world = "🌍"; const X = <template>{{world}}</template>;`, {
|
|
121
|
+
visitors: () => ({
|
|
122
|
+
Identifier: (node) => (node.name = node.name.toUpperCase()),
|
|
123
|
+
GlimmerPathExpression(node) {
|
|
124
|
+
node.original = node.original.toUpperCase();
|
|
125
|
+
if (node.head) node.head.name = node.original;
|
|
126
|
+
},
|
|
127
|
+
}),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
print(ast.program);
|
|
131
|
+
// => 'const WORLD = "🌍";\nconst X = <template>{{WORLD}}</template>;'
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Collecting Glimmer comments into `program.comments`** — useful when adapting the AST for ESLint, which reads comments from the Program node:
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
const ast = toTree(source, {
|
|
138
|
+
visitors: (outerAst) => {
|
|
139
|
+
outerAst.program.comments = [...(outerAst.comments ?? [])];
|
|
140
|
+
const push = (node) => outerAst.program.comments.push(node);
|
|
141
|
+
return {
|
|
142
|
+
GlimmerCommentStatement: push,
|
|
143
|
+
GlimmerMustacheCommentStatement: push,
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Removing nodes mid-traversal** — siblings are splice-safe:
|
|
150
|
+
|
|
151
|
+
```js
|
|
152
|
+
toTree(source, {
|
|
153
|
+
visitors: () => ({
|
|
154
|
+
GlimmerMustacheCommentStatement(node, path) {
|
|
155
|
+
const siblings = path.parent?.body ?? path.parent?.children;
|
|
156
|
+
const idx = siblings?.indexOf(node) ?? -1;
|
|
157
|
+
if (idx >= 0) siblings.splice(idx, 1);
|
|
158
|
+
},
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
60
163
|
## Examples
|
|
61
164
|
|
|
62
165
|
The [`examples/`](./examples) directory contains ready-to-run integrations:
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -30,11 +30,6 @@ export interface VisitorPath {
|
|
|
30
30
|
export interface ParseOptions {
|
|
31
31
|
filePath?: string;
|
|
32
32
|
templateOnly?: boolean;
|
|
33
|
-
/**
|
|
34
|
-
* Include `parent` references on Glimmer AST nodes.
|
|
35
|
-
* Defaults to `true`. Set to `false` for JSON-serializable output.
|
|
36
|
-
*/
|
|
37
|
-
includeParentLinks?: boolean;
|
|
38
33
|
/**
|
|
39
34
|
* Custom JS/TS parser. Called with the placeholder JS string
|
|
40
35
|
* (templates replaced with backtick expressions of equal length).
|
|
@@ -42,15 +37,24 @@ export interface ParseOptions {
|
|
|
42
37
|
*/
|
|
43
38
|
parser?: (placeholderJS: string) => { ast: ASTNode; [key: string]: unknown };
|
|
44
39
|
/**
|
|
45
|
-
* Callbacks
|
|
46
|
-
*
|
|
40
|
+
* Callbacks fired on each node during traversal — outer JS/TS nodes AND
|
|
41
|
+
* spliced Glimmer subtrees — so callers can gather information or mutate
|
|
42
|
+
* the tree in a single pass.
|
|
43
|
+
*
|
|
44
|
+
* Pass either a plain handler map, or a factory `(outerAst) => handlers`
|
|
45
|
+
* that's called once after parsing (before template splicing) when you
|
|
46
|
+
* need a view of the raw JS/TS tree up front.
|
|
47
|
+
*
|
|
48
|
+
* The pseudo-type `GlimmerBlockParams` fires on any node that carries
|
|
49
|
+
* a `blockParams` array.
|
|
47
50
|
*/
|
|
48
|
-
visitors?:
|
|
49
|
-
[glimmerNodeType: string]: (node: ASTNode, path: VisitorPath) => void;
|
|
50
|
-
GlimmerBlockParams?: (node: ASTNode, path: VisitorPath) => void;
|
|
51
|
-
};
|
|
51
|
+
visitors?: VisitorMap | ((outerAst: ASTNode) => VisitorMap | null | undefined);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
export type VisitorMap = {
|
|
55
|
+
[nodeType: string]: (node: ASTNode, path: VisitorPath) => void;
|
|
56
|
+
};
|
|
57
|
+
|
|
54
58
|
export class DocumentLines {
|
|
55
59
|
constructor(source: string);
|
|
56
60
|
positionToOffset(pos: Position): number;
|
package/src/parse.js
CHANGED
|
@@ -33,21 +33,22 @@ const PLACEHOLDER_TYPES = new Set([
|
|
|
33
33
|
* @param {string} [options.filePath] - File path for language detection
|
|
34
34
|
* @param {boolean} [options.templateOnly] - Parse as raw Glimmer template content (for .hbs)
|
|
35
35
|
* @param {function} [options.parser] - Custom JS/TS parser: (placeholderJS) => { ast, scopeManager?, visitorKeys?, services?, ... }
|
|
36
|
-
* @param {object}
|
|
36
|
+
* @param {object|function} [options.visitors] - Either a map of `{ [Type]: (node, path) => void }`
|
|
37
|
+
* handlers, or a factory `(outerAst) => handlers` invoked once after parsing (before any
|
|
38
|
+
* template splicing) to give callers a view of the raw JS/TS tree. Handlers fire on every
|
|
39
|
+
* node during traversal — outer JS/TS nodes AND spliced Glimmer subtrees — in a single pass.
|
|
40
|
+
* The pseudo-type `GlimmerBlockParams` fires on any node that carries `blockParams`.
|
|
37
41
|
* @return {object}
|
|
38
42
|
*/
|
|
39
43
|
export function toTree(source, options = {}) {
|
|
40
|
-
const templateOpts = options.includeParentLinks === false ? { includeParentLinks: false } : {};
|
|
41
|
-
|
|
42
44
|
if (options.templateOnly) {
|
|
43
|
-
return processTemplate(source, new DocumentLines(source), [0, source.length]
|
|
45
|
+
return processTemplate(source, new DocumentLines(source), [0, source.length]);
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
let parseResults = preprocessor.parse(source);
|
|
47
49
|
let js = toPlaceholderJS(source, parseResults);
|
|
48
50
|
|
|
49
51
|
const useCustomParser = !!options.parser;
|
|
50
|
-
const visitors = options.visitors || null;
|
|
51
52
|
|
|
52
53
|
// Parse the placeholder JS — use custom parser or default oxc
|
|
53
54
|
let result;
|
|
@@ -73,8 +74,24 @@ export function toTree(source, options = {}) {
|
|
|
73
74
|
};
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
//
|
|
77
|
-
|
|
77
|
+
// Resolve user visitors against the outer AST. A plain object is used
|
|
78
|
+
// as-is; a factory is called once so callers can introspect the raw
|
|
79
|
+
// JS/TS tree before any template splicing. Default to `{}` so downstream
|
|
80
|
+
// dispatch can be a bare `visitors[type]` lookup without null-guards.
|
|
81
|
+
const visitors =
|
|
82
|
+
typeof options.visitors === "function"
|
|
83
|
+
? (options.visitors(result.ast) ?? {})
|
|
84
|
+
: (options.visitors ?? {});
|
|
85
|
+
const hasVisitors = Object.keys(visitors).length > 0;
|
|
86
|
+
// Guard against dispatching a handler twice on the same node.
|
|
87
|
+
// Visitors that relocate nodes (e.g. moving Glimmer comments into
|
|
88
|
+
// `program.comments`) would otherwise fire a second time when the walk
|
|
89
|
+
// reaches the new location.
|
|
90
|
+
const seen = new WeakSet();
|
|
91
|
+
const hasTemplates = parseResults.length > 0;
|
|
92
|
+
|
|
93
|
+
// Nothing to walk — attach visitor keys and return.
|
|
94
|
+
if (!hasTemplates && !hasVisitors) {
|
|
78
95
|
if (useCustomParser) {
|
|
79
96
|
result.visitorKeys = { ...result.visitorKeys, ...glimmerVisitorKeys };
|
|
80
97
|
return result;
|
|
@@ -83,11 +100,11 @@ export function toTree(source, options = {}) {
|
|
|
83
100
|
return result.ast;
|
|
84
101
|
}
|
|
85
102
|
|
|
86
|
-
const codeLines = new DocumentLines(source);
|
|
103
|
+
const codeLines = hasTemplates ? new DocumentLines(source) : null;
|
|
87
104
|
const templateInfos = [];
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
105
|
+
const templateRangeByStart = hasTemplates
|
|
106
|
+
? new Map(parseResults.map((r) => [r.range.startUtf16Codepoint, r]))
|
|
107
|
+
: null;
|
|
91
108
|
|
|
92
109
|
// Process a matched placeholder node: create Glimmer AST and tokens
|
|
93
110
|
function processPlaceholder(parseResult) {
|
|
@@ -98,7 +115,7 @@ export function toTree(source, options = {}) {
|
|
|
98
115
|
];
|
|
99
116
|
let fullRange = [parseResult.range.startUtf16Codepoint, parseResult.range.endUtf16Codepoint];
|
|
100
117
|
|
|
101
|
-
const { ast } = processTemplate(templateContent, codeLines, contentRange
|
|
118
|
+
const { ast } = processTemplate(templateContent, codeLines, contentRange);
|
|
102
119
|
|
|
103
120
|
// Fix the Template root to cover the full <template>...</template> range
|
|
104
121
|
ast.range = fullRange;
|
|
@@ -153,101 +170,57 @@ export function toTree(source, options = {}) {
|
|
|
153
170
|
return parseResult;
|
|
154
171
|
}
|
|
155
172
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const path = { node, parent: parentPath?.node ?? null, parentPath };
|
|
160
|
-
|
|
161
|
-
if (visitors && node.type.startsWith("Glimmer")) {
|
|
162
|
-
const handler = visitors[node.type];
|
|
163
|
-
if (handler) handler(node, path);
|
|
164
|
-
if ("blockParams" in node && visitors.GlimmerBlockParams) {
|
|
165
|
-
visitors.GlimmerBlockParams(node, path);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const keys = glimmerVisitorKeys[node.type];
|
|
170
|
-
if (!keys) return;
|
|
171
|
-
for (const key of keys) {
|
|
172
|
-
const child = node[key];
|
|
173
|
-
if (!child) continue;
|
|
174
|
-
if (Array.isArray(child)) {
|
|
175
|
-
for (const item of child) {
|
|
176
|
-
walkGlimmerTree(item, path);
|
|
177
|
-
}
|
|
178
|
-
} else if (typeof child === "object" && child.type) {
|
|
179
|
-
walkGlimmerTree(child, path);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (useCustomParser) {
|
|
185
|
-
// Custom parser path: mutate the parser's AST in-place, invoke visitors.
|
|
186
|
-
// Use the parser's visitorKeys to traverse efficiently (avoids Object.keys).
|
|
187
|
-
const parserVisitorKeys = result.visitorKeys || {};
|
|
188
|
-
|
|
189
|
-
function visitNode(node, parentPath) {
|
|
190
|
-
if (!node || typeof node !== "object" || !node.type) return;
|
|
191
|
-
|
|
192
|
-
const path = { node, parent: parentPath?.node ?? null, parentPath };
|
|
193
|
-
|
|
194
|
-
if (PLACEHOLDER_TYPES.has(node.type)) {
|
|
173
|
+
result.ast = walk(result.ast, null, {
|
|
174
|
+
_(node, { next, visit, state }) {
|
|
175
|
+
if (hasTemplates && PLACEHOLDER_TYPES.has(node.type)) {
|
|
195
176
|
const parseResult = matchPlaceholder(node);
|
|
196
177
|
if (parseResult) {
|
|
197
178
|
const ast = processPlaceholder(parseResult);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
return;
|
|
179
|
+
// Zimmerframe treats a visitor that returns a node as having
|
|
180
|
+
// taken responsibility for the subtree — it splices the result
|
|
181
|
+
// in but does NOT descend into it. So when any handlers are
|
|
182
|
+
// configured we re-enter the walk manually via `visit()` to
|
|
183
|
+
// dispatch them across the Glimmer nodes. With no handlers the
|
|
184
|
+
// walk would be pure overhead, so just return the subtree.
|
|
185
|
+
return hasVisitors ? visit(ast, null) : ast;
|
|
206
186
|
}
|
|
207
187
|
}
|
|
208
188
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
} else if (typeof child === "object" && child.type) {
|
|
222
|
-
visitNode(child, path);
|
|
189
|
+
const path = {
|
|
190
|
+
node,
|
|
191
|
+
parent: state?.parentPath?.node ?? null,
|
|
192
|
+
parentPath: state?.parentPath ?? null,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
if (hasVisitors && !seen.has(node)) {
|
|
196
|
+
seen.add(node);
|
|
197
|
+
const handler = visitors[node.type];
|
|
198
|
+
if (handler) handler(node, path);
|
|
199
|
+
if ("blockParams" in node && visitors.GlimmerBlockParams) {
|
|
200
|
+
visitors.GlimmerBlockParams(node, path);
|
|
223
201
|
}
|
|
224
202
|
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
visitNode(result.ast, null);
|
|
228
|
-
} else {
|
|
229
|
-
// Default oxc path: use zimmerframe walk (returns new tree)
|
|
230
|
-
result.ast = walk(result.ast, null, {
|
|
231
|
-
_(node, { next }) {
|
|
232
|
-
if (PLACEHOLDER_TYPES.has(node.type)) {
|
|
233
|
-
const parseResult = matchPlaceholder(node);
|
|
234
|
-
if (parseResult) {
|
|
235
|
-
return processPlaceholder(parseResult);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
next();
|
|
239
|
-
},
|
|
240
|
-
});
|
|
241
203
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
walkGlimmerTree(ti.ast, null);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
204
|
+
next({ parentPath: path });
|
|
205
|
+
},
|
|
206
|
+
});
|
|
249
207
|
|
|
250
208
|
// Splice template tokens into the AST token stream.
|
|
209
|
+
//
|
|
210
|
+
// `tokens` is the flat lexed stream (keywords, punctuators, identifiers,
|
|
211
|
+
// literals) that ESLint, formatters, and source-map tooling consume —
|
|
212
|
+
// `SourceCode.getTokens()` reads it directly.
|
|
213
|
+
//
|
|
214
|
+
// We replaced each <template>...</template> region with a backtick
|
|
215
|
+
// placeholder before handing the source to the JS/TS parser, so the
|
|
216
|
+
// parser's tokens for those ranges describe the placeholder, not the
|
|
217
|
+
// real source. Here we swap them out for the real lexemes:
|
|
218
|
+
// 1. a fabricated `<template>` Punctuator (added in processPlaceholder)
|
|
219
|
+
// 2. the Glimmer AST's own tokens (from transforms.js)
|
|
220
|
+
// 3. a fabricated `</template>` Punctuator
|
|
221
|
+
// so consumers see a position-accurate token stream matching the
|
|
222
|
+
// original source byte-for-byte across JS and Glimmer regions.
|
|
223
|
+
//
|
|
251
224
|
// Tokens are sorted by range, so use binary search for O(log n) lookup.
|
|
252
225
|
const astRoot = result.ast.program || result.ast;
|
|
253
226
|
if (astRoot.tokens) {
|
package/src/transforms.js
CHANGED
|
@@ -61,6 +61,11 @@ export const glimmerVisitorKeys = (() => {
|
|
|
61
61
|
// VarHead: name, original
|
|
62
62
|
// Block: blockParams
|
|
63
63
|
const _desc = { value: undefined, configurable: true, enumerable: true, writable: true };
|
|
64
|
+
const _parentDesc = { value: null, configurable: true, enumerable: false, writable: true };
|
|
65
|
+
function setParent(node, parent) {
|
|
66
|
+
_parentDesc.value = parent;
|
|
67
|
+
Object.defineProperty(node, "parent", _parentDesc);
|
|
68
|
+
}
|
|
64
69
|
function defOwn(obj, key) {
|
|
65
70
|
_desc.value = obj[key];
|
|
66
71
|
Object.defineProperty(obj, key, _desc);
|
|
@@ -168,12 +173,7 @@ function buildTokenStream(rawTokens, comments, textNodes) {
|
|
|
168
173
|
* positions, create parts/blockParamNodes, nullify empty hashes, and
|
|
169
174
|
* prefix types. No separate collect-then-transform loop.
|
|
170
175
|
*/
|
|
171
|
-
export function processTemplate(
|
|
172
|
-
templateContent,
|
|
173
|
-
codeLines,
|
|
174
|
-
templateRange,
|
|
175
|
-
{ includeParentLinks = true } = {},
|
|
176
|
-
) {
|
|
176
|
+
export function processTemplate(templateContent, codeLines, templateRange) {
|
|
177
177
|
const offset = templateRange[0];
|
|
178
178
|
const docLines = offset === 0 ? codeLines : new DocumentLines(templateContent);
|
|
179
179
|
|
|
@@ -187,7 +187,6 @@ export function processTemplate(
|
|
|
187
187
|
});
|
|
188
188
|
|
|
189
189
|
const ast = glimmerPreprocess(templateContent, { mode: "codemod" });
|
|
190
|
-
const allNodes = [];
|
|
191
190
|
const comments = [];
|
|
192
191
|
const textNodes = [];
|
|
193
192
|
const emptyTextNodes = [];
|
|
@@ -197,8 +196,7 @@ export function processTemplate(
|
|
|
197
196
|
// children using raw visitor keys. Type prefixing happens inline
|
|
198
197
|
// AFTER recursing (so children see the original type during lookup).
|
|
199
198
|
function visit(n, parent) {
|
|
200
|
-
n
|
|
201
|
-
allNodes.push(n);
|
|
199
|
+
setParent(n, parent);
|
|
202
200
|
|
|
203
201
|
// Categorize
|
|
204
202
|
if (n.type === "CommentStatement" || n.type === "MustacheCommentStatement") {
|
|
@@ -257,18 +255,17 @@ export function processTemplate(
|
|
|
257
255
|
n.name = n.tag;
|
|
258
256
|
const p = n.path.head;
|
|
259
257
|
const partRange = toFileRange(p.loc);
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
];
|
|
258
|
+
const part = {
|
|
259
|
+
type: "GlimmerElementNodePart",
|
|
260
|
+
original: p.original,
|
|
261
|
+
name: p.original,
|
|
262
|
+
range: partRange,
|
|
263
|
+
start: partRange[0],
|
|
264
|
+
end: partRange[1],
|
|
265
|
+
loc: toFileLoc(partRange),
|
|
266
|
+
};
|
|
267
|
+
setParent(part, n);
|
|
268
|
+
n.parts = [part];
|
|
272
269
|
}
|
|
273
270
|
|
|
274
271
|
// Create blockParamNodes
|
|
@@ -276,27 +273,31 @@ export function processTemplate(
|
|
|
276
273
|
if (n.params && n.params.length === n.blockParams.length) {
|
|
277
274
|
n.blockParamNodes = n.params.map((p) => {
|
|
278
275
|
const range = toFileRange(p.loc);
|
|
279
|
-
|
|
276
|
+
const bp = {
|
|
280
277
|
type: "GlimmerBlockParam",
|
|
281
278
|
name: p.original || p.name,
|
|
282
279
|
original: p.original,
|
|
283
|
-
parent: n,
|
|
284
280
|
range,
|
|
285
281
|
start: range[0],
|
|
286
282
|
end: range[1],
|
|
287
283
|
loc: toFileLoc(range),
|
|
288
284
|
};
|
|
285
|
+
setParent(bp, n);
|
|
286
|
+
return bp;
|
|
289
287
|
});
|
|
290
288
|
} else {
|
|
291
|
-
n.blockParamNodes = n.blockParams.map((bpName) =>
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
289
|
+
n.blockParamNodes = n.blockParams.map((bpName) => {
|
|
290
|
+
const bp = {
|
|
291
|
+
type: "GlimmerBlockParam",
|
|
292
|
+
name: bpName,
|
|
293
|
+
range: [n.range[0], n.range[1]],
|
|
294
|
+
start: n.range[0],
|
|
295
|
+
end: n.range[1],
|
|
296
|
+
loc: toFileLoc(n.range),
|
|
297
|
+
};
|
|
298
|
+
setParent(bp, n);
|
|
299
|
+
return bp;
|
|
300
|
+
});
|
|
300
301
|
}
|
|
301
302
|
}
|
|
302
303
|
|
|
@@ -337,13 +338,5 @@ export function processTemplate(
|
|
|
337
338
|
ast.tokens = buildTokenStream(tokenize(templateContent, codeLines, offset), comments, textNodes);
|
|
338
339
|
ast.contents = templateContent;
|
|
339
340
|
|
|
340
|
-
if (!includeParentLinks) {
|
|
341
|
-
for (const n of allNodes) {
|
|
342
|
-
delete n.parent;
|
|
343
|
-
if (n.parts) for (const p of n.parts) delete p.parent;
|
|
344
|
-
if (n.blockParamNodes) for (const p of n.blockParamNodes) delete p.parent;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
341
|
return { ast, comments };
|
|
349
342
|
}
|