ember-estree 0.1.2 → 0.2.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/package.json +7 -3
- package/src/index.d.ts +95 -0
- package/src/parse.js +67 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ember-estree",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "ESTree generator for gjs and gts file used by ember",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"AST",
|
|
@@ -22,12 +22,16 @@
|
|
|
22
22
|
],
|
|
23
23
|
"type": "module",
|
|
24
24
|
"main": "src/index.js",
|
|
25
|
+
"types": "src/index.d.ts",
|
|
25
26
|
"exports": {
|
|
26
|
-
".":
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./src/index.d.ts",
|
|
29
|
+
"default": "./src/index.js"
|
|
30
|
+
}
|
|
27
31
|
},
|
|
28
32
|
"dependencies": {
|
|
29
33
|
"@glimmer/syntax": "^0.95.0",
|
|
30
|
-
"content-tag
|
|
34
|
+
"content-tag": "^4.1.0",
|
|
31
35
|
"ember-template-recast": "^6.1.5",
|
|
32
36
|
"oxc-parser": "^0.119.0",
|
|
33
37
|
"zimmerframe": "^1.1.4"
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options accepted by `parse` and `toTree`.
|
|
3
|
+
*/
|
|
4
|
+
export interface ParseOptions {
|
|
5
|
+
/** Path to the file being parsed, used to determine the language (js/ts). */
|
|
6
|
+
filePath?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A 1-based line / 0-based column position, matching ESTree and Glimmer
|
|
11
|
+
* conventions.
|
|
12
|
+
*/
|
|
13
|
+
export interface Position {
|
|
14
|
+
line: number;
|
|
15
|
+
column: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Minimal shape shared by every AST node (ESTree, TypeScript, and Glimmer).
|
|
20
|
+
*/
|
|
21
|
+
export interface ASTNode {
|
|
22
|
+
type: string;
|
|
23
|
+
start?: number;
|
|
24
|
+
end?: number;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The `File`-like wrapper returned by `toTree` and `parse`.
|
|
30
|
+
*
|
|
31
|
+
* Mirrors the shape produced internally:
|
|
32
|
+
* ```
|
|
33
|
+
* { type: "File", program: Program, comments: Comment[], start, end }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export interface FileNode extends ASTNode {
|
|
37
|
+
type: "File";
|
|
38
|
+
program: ASTNode;
|
|
39
|
+
comments: ASTNode[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Converts between character offsets and line/column positions within a
|
|
44
|
+
* source string.
|
|
45
|
+
*/
|
|
46
|
+
export class DocumentLines {
|
|
47
|
+
constructor(source: string);
|
|
48
|
+
|
|
49
|
+
/** Converts a `{ line, column }` position to a character offset. */
|
|
50
|
+
positionToOffset(pos: Position): number;
|
|
51
|
+
|
|
52
|
+
/** Converts a character offset to a `{ line, column }` position. */
|
|
53
|
+
offsetToPosition(offset: number): Position;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parse Ember .gjs/.gts source code and return a File-like ESTree-compatible
|
|
58
|
+
* AST with embedded Glimmer template nodes.
|
|
59
|
+
*
|
|
60
|
+
* @param source The raw source code of the file.
|
|
61
|
+
* @param options Optional parse options.
|
|
62
|
+
* @returns A `File`-shaped object with a `.program` property.
|
|
63
|
+
*/
|
|
64
|
+
export function toTree(source: string, options?: ParseOptions): FileNode;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Parse Ember .gjs/.gts source code into an ESTree-compatible AST with
|
|
68
|
+
* embedded Glimmer template nodes.
|
|
69
|
+
*
|
|
70
|
+
* @param source The source code to parse.
|
|
71
|
+
* @param options Optional parse options.
|
|
72
|
+
* @returns The ESTree-compatible AST.
|
|
73
|
+
*/
|
|
74
|
+
export function parse(source: string, options?: ParseOptions): FileNode;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Recursively print an AST node back to source code.
|
|
78
|
+
*
|
|
79
|
+
* Handles ESTree, TypeScript, and Glimmer template node types.
|
|
80
|
+
* JSX nodes are not supported — Ember uses Glimmer templates instead.
|
|
81
|
+
*
|
|
82
|
+
* @param node The AST node to print.
|
|
83
|
+
* @returns The printed source string.
|
|
84
|
+
*/
|
|
85
|
+
export function print(node: ASTNode): string;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Build and return the Glimmer visitor keys map with a `"Glimmer"` prefix on
|
|
89
|
+
* every key (e.g. `"GlimmerElementNode"`).
|
|
90
|
+
*
|
|
91
|
+
* The result is cached after the first call.
|
|
92
|
+
*
|
|
93
|
+
* @returns A map of Glimmer node type names to arrays of child-property names.
|
|
94
|
+
*/
|
|
95
|
+
export function buildGlimmerVisitorKeys(): Record<string, string[]>;
|
package/src/parse.js
CHANGED
|
@@ -27,19 +27,21 @@
|
|
|
27
27
|
|
|
28
28
|
import { parseSync } from "oxc-parser";
|
|
29
29
|
import templateRecast from "ember-template-recast";
|
|
30
|
-
import {
|
|
30
|
+
import { Preprocessor } from "content-tag";
|
|
31
31
|
import { walk } from "zimmerframe";
|
|
32
32
|
|
|
33
33
|
import { processGlimmerTemplate } from "./transforms.js";
|
|
34
34
|
|
|
35
|
+
const preprocessor = new Preprocessor();
|
|
36
|
+
|
|
35
37
|
/**
|
|
36
38
|
* @param {string} source
|
|
37
39
|
* @param {object} options
|
|
38
40
|
* @return {object} A File-like AST with a `.program` property
|
|
39
41
|
*/
|
|
40
42
|
export function toTree(source, options = {}) {
|
|
41
|
-
let
|
|
42
|
-
let js =
|
|
43
|
+
let parseResults = preprocessor.parse(source);
|
|
44
|
+
let js = toPlaceholderJS(source, parseResults);
|
|
43
45
|
|
|
44
46
|
let filename = options.filePath || "input.ts";
|
|
45
47
|
let oxcResult = parseSync(filename, js);
|
|
@@ -53,21 +55,26 @@ export function toTree(source, options = {}) {
|
|
|
53
55
|
end: oxcResult.program.end,
|
|
54
56
|
};
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
// content-tag v4 provides UTF-16 codepoint offsets that match
|
|
59
|
+
// JavaScript string indices and oxc-parser character offsets directly,
|
|
60
|
+
// so no byte-to-character conversion is needed.
|
|
58
61
|
outerAST = walk(outerAST, null, {
|
|
59
62
|
_(node, { next }) {
|
|
60
63
|
if (isExpressionPlaceholder(node) || isClassMemberPlaceholder(node)) {
|
|
61
64
|
let parseResult = parseResults.find((r) => {
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
return (
|
|
66
|
+
node.start === r.range.startUtf16Codepoint && node.end === r.range.endUtf16Codepoint
|
|
67
|
+
);
|
|
64
68
|
});
|
|
65
69
|
|
|
66
|
-
let content =
|
|
70
|
+
let content = parseResult.contents;
|
|
67
71
|
let templateAST = templateRecast.parse(content);
|
|
68
72
|
|
|
69
|
-
let contentOffset = parseResult.contentRange.
|
|
70
|
-
let templateRange = [
|
|
73
|
+
let contentOffset = parseResult.contentRange.startUtf16Codepoint;
|
|
74
|
+
let templateRange = [
|
|
75
|
+
parseResult.range.startUtf16Codepoint,
|
|
76
|
+
parseResult.range.endUtf16Codepoint,
|
|
77
|
+
];
|
|
71
78
|
|
|
72
79
|
return processGlimmerTemplate(templateAST, {
|
|
73
80
|
contentOffset,
|
|
@@ -117,3 +124,53 @@ function isClassMemberPlaceholder(node) {
|
|
|
117
124
|
node.computed && node.key?.type === "CallExpression" && node.key.callee?.name === "_TEMPLATE_"
|
|
118
125
|
);
|
|
119
126
|
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Replaces <template>...</template> regions in source with
|
|
130
|
+
* placeholder expressions of the same character length that
|
|
131
|
+
* are valid JavaScript, so oxc-parser can parse them.
|
|
132
|
+
*
|
|
133
|
+
* Expression templates become: TEMPLATE_TEMPLATE(`...`)
|
|
134
|
+
* Class member templates become: [_TEMPLATE_(`...`)] = 0;
|
|
135
|
+
*
|
|
136
|
+
* Both placeholder forms use exactly 21 characters for the
|
|
137
|
+
* opening + closing wrappers, matching the original
|
|
138
|
+
* <template> (10) + </template> (11) = 21 character overhead.
|
|
139
|
+
*
|
|
140
|
+
* @param {string} source
|
|
141
|
+
* @param {Array<object>} parseResults
|
|
142
|
+
* @returns {string}
|
|
143
|
+
*/
|
|
144
|
+
function toPlaceholderJS(source, parseResults) {
|
|
145
|
+
let result = source;
|
|
146
|
+
let offset = 0;
|
|
147
|
+
|
|
148
|
+
for (let pr of parseResults) {
|
|
149
|
+
let start = pr.range.startUtf16Codepoint;
|
|
150
|
+
let end = pr.range.endUtf16Codepoint;
|
|
151
|
+
|
|
152
|
+
let openingTag, closingTag;
|
|
153
|
+
switch (pr.type) {
|
|
154
|
+
case "expression":
|
|
155
|
+
openingTag = "TEMPLATE_TEMPLATE(`";
|
|
156
|
+
closingTag = "`)";
|
|
157
|
+
break;
|
|
158
|
+
case "class-member":
|
|
159
|
+
openingTag = "[_TEMPLATE_(`";
|
|
160
|
+
closingTag = "`)] = 0;";
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let content = source.slice(
|
|
165
|
+
pr.contentRange.startUtf16Codepoint,
|
|
166
|
+
pr.contentRange.endUtf16Codepoint,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
let replacement = openingTag + content + closingTag;
|
|
170
|
+
|
|
171
|
+
result = result.slice(0, start + offset) + replacement + result.slice(end + offset);
|
|
172
|
+
offset += replacement.length - (end - start);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return result;
|
|
176
|
+
}
|