@angular-eslint/template-parser 15.2.2-alpha.9 → 16.0.0-alpha.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,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.convertElementSourceSpanToLoc = exports.convertNodeSourceSpanToLoc = void 0;
|
|
4
|
+
const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
|
|
5
|
+
function convertNodeSourceSpanToLoc(sourceSpan) {
|
|
6
|
+
return {
|
|
7
|
+
start: {
|
|
8
|
+
line: sourceSpan.start.line + 1,
|
|
9
|
+
column: sourceSpan.start.col,
|
|
10
|
+
},
|
|
11
|
+
end: {
|
|
12
|
+
line: sourceSpan.end.line + 1,
|
|
13
|
+
column: sourceSpan.end.col,
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
exports.convertNodeSourceSpanToLoc = convertNodeSourceSpanToLoc;
|
|
18
|
+
function convertElementSourceSpanToLoc(context, node) {
|
|
19
|
+
if (node.type !== 'Element$1') {
|
|
20
|
+
// We explicitly throw an exception since this function should not be used
|
|
21
|
+
// with non-element nodes, e.g. `TextAttribute` or `MethodDefinition`, etc.
|
|
22
|
+
throw new Error('convertElementSourceSpanToLoc is intended to be used only with elements.');
|
|
23
|
+
}
|
|
24
|
+
// Void elements are "self-closed" elements, e.g. `<img />` or `<area />`.
|
|
25
|
+
// The Angular compiler explicitly doesn't set the end source span for void
|
|
26
|
+
// elements, but we still have to find its location to be able to report failures.
|
|
27
|
+
if ((0, bundled_angular_compiler_1.getHtmlTagDefinition)(node.name).isVoid) {
|
|
28
|
+
// Fallback to the original `node` if the
|
|
29
|
+
// `tryToFindTheVoidNodeThatMatchesTheSourceSpan` returns nothing.
|
|
30
|
+
node = (tryToFindTheVoidNodeThatMatchesTheSourceSpan(context, node) ||
|
|
31
|
+
node);
|
|
32
|
+
}
|
|
33
|
+
return convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
34
|
+
}
|
|
35
|
+
exports.convertElementSourceSpanToLoc = convertElementSourceSpanToLoc;
|
|
36
|
+
function tryToFindTheVoidNodeThatMatchesTheSourceSpan(context, node) {
|
|
37
|
+
// Previously, `codelyzer` used `TemplateParser` to parse a template into an AST tree.
|
|
38
|
+
// The `TemplateParser` used `HtmlParser`, because `HtmlParser` still sets the end span
|
|
39
|
+
// for void elements.
|
|
40
|
+
const { rootNodes } = getHtmlParser().parse(context.getSourceCode().getText(), context.getFilename());
|
|
41
|
+
return lookupTheVoidNode(rootNodes, node.sourceSpan);
|
|
42
|
+
}
|
|
43
|
+
function lookupTheVoidNode(rootNodes, sourceSpan) {
|
|
44
|
+
for (const node of rootNodes) {
|
|
45
|
+
if (
|
|
46
|
+
// We can't compare by `node.sourceSpan == sourceSpan` since references
|
|
47
|
+
// will differ. But comparing `line` and` offset` is the
|
|
48
|
+
// correct way, because they will not differ.
|
|
49
|
+
node.sourceSpan.start.line === sourceSpan.start.line &&
|
|
50
|
+
node.sourceSpan.start.offset === sourceSpan.start.offset) {
|
|
51
|
+
return node;
|
|
52
|
+
}
|
|
53
|
+
// `HtmlParser` will return a list of root nodes, these nodes
|
|
54
|
+
// can be either text or elements. Elements might have child elements.
|
|
55
|
+
if (node instanceof bundled_angular_compiler_1.Element) {
|
|
56
|
+
const voidNodeBeingLookedUp = lookupTheVoidNode(node.children, sourceSpan);
|
|
57
|
+
if (voidNodeBeingLookedUp !== null) {
|
|
58
|
+
return voidNodeBeingLookedUp;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
let htmlParser = null;
|
|
65
|
+
// Initialize the `HtmlParser` class lazily only when the function is
|
|
66
|
+
// invoked for the first time.
|
|
67
|
+
function getHtmlParser() {
|
|
68
|
+
return htmlParser || (htmlParser = new bundled_angular_compiler_1.HtmlParser());
|
|
69
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1 +1,215 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parse = exports.parseForESLint = exports.createTemplateParseError = exports.TemplateParseError = void 0;
|
|
4
|
+
const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
|
|
5
|
+
const eslint_scope_1 = require("eslint-scope");
|
|
6
|
+
const convert_source_span_to_loc_1 = require("./convert-source-span-to-loc");
|
|
7
|
+
const KEYS = {
|
|
8
|
+
ASTWithSource: ['ast'],
|
|
9
|
+
Binary: ['left', 'right'],
|
|
10
|
+
BoundAttribute: ['value'],
|
|
11
|
+
BoundEvent: ['handler'],
|
|
12
|
+
BoundText: ['value'],
|
|
13
|
+
Conditional: ['condition', 'trueExp', 'falseExp'],
|
|
14
|
+
Element$1: ['children', 'inputs', 'outputs', 'attributes'],
|
|
15
|
+
Interpolation: ['expressions'],
|
|
16
|
+
PrefixNot: ['expression'],
|
|
17
|
+
Program: ['templateNodes'],
|
|
18
|
+
PropertyRead: ['receiver'],
|
|
19
|
+
Template: ['templateAttrs', 'children', 'inputs'],
|
|
20
|
+
BindingPipe: ['exp'],
|
|
21
|
+
};
|
|
22
|
+
function fallbackKeysFilter(key) {
|
|
23
|
+
let value = null;
|
|
24
|
+
return (key !== 'comments' &&
|
|
25
|
+
key !== 'leadingComments' &&
|
|
26
|
+
key !== 'loc' &&
|
|
27
|
+
key !== 'parent' &&
|
|
28
|
+
key !== 'range' &&
|
|
29
|
+
key !== 'tokens' &&
|
|
30
|
+
key !== 'trailingComments' &&
|
|
31
|
+
(value = this[key]) !== null &&
|
|
32
|
+
typeof value === 'object' &&
|
|
33
|
+
(typeof value.type === 'string' || Array.isArray(value)));
|
|
34
|
+
}
|
|
35
|
+
function getFallbackKeys(node) {
|
|
36
|
+
return Object.keys(node).filter(fallbackKeysFilter, node);
|
|
37
|
+
}
|
|
38
|
+
function isNode(node) {
|
|
39
|
+
return (node !== null &&
|
|
40
|
+
typeof node === 'object' &&
|
|
41
|
+
typeof node.type === 'string');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* ESLint requires all Nodes to have `type` and `loc` properties before it can
|
|
45
|
+
* work with the custom AST.
|
|
46
|
+
*/
|
|
47
|
+
function preprocessNode(node) {
|
|
48
|
+
let i = 0;
|
|
49
|
+
let j = 0;
|
|
50
|
+
const keys = KEYS[node.type] || getFallbackKeys(node);
|
|
51
|
+
if (!node.loc && node.sourceSpan) {
|
|
52
|
+
node.loc = (0, convert_source_span_to_loc_1.convertNodeSourceSpanToLoc)(node.sourceSpan);
|
|
53
|
+
}
|
|
54
|
+
for (i = 0; i < keys.length; ++i) {
|
|
55
|
+
const child = node[keys[i]];
|
|
56
|
+
const isArr = Array.isArray(child);
|
|
57
|
+
if (child.type !== undefined) {
|
|
58
|
+
// Angular sometimes uses a prop called type already
|
|
59
|
+
child.__originalType = child.type;
|
|
60
|
+
}
|
|
61
|
+
if (!isArr && !child.type) {
|
|
62
|
+
child.type = child.constructor.name;
|
|
63
|
+
}
|
|
64
|
+
if (isArr) {
|
|
65
|
+
for (j = 0; j < child.length; ++j) {
|
|
66
|
+
const c = child[j];
|
|
67
|
+
if (c.type !== undefined) {
|
|
68
|
+
// Angular sometimes uses a prop called type already
|
|
69
|
+
c.__originalType = c.type;
|
|
70
|
+
}
|
|
71
|
+
// Pay attention to the condition `typeof c.type === number`,
|
|
72
|
+
// Angular compiler sets `type` property for some AST nodes,
|
|
73
|
+
// e.g. for the `BoundAttribute`, which is a `BindingType`.
|
|
74
|
+
if (!c.type || typeof c.type === 'number') {
|
|
75
|
+
c.type = c.constructor.name;
|
|
76
|
+
}
|
|
77
|
+
if (isNode(c)) {
|
|
78
|
+
preprocessNode(c);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else if (isNode(child)) {
|
|
83
|
+
preprocessNode(child);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function getStartSourceSpanFromAST(ast) {
|
|
88
|
+
let startSourceSpan = null;
|
|
89
|
+
ast.templateNodes.forEach((node) => {
|
|
90
|
+
const nodeSourceSpan = node.startSourceSpan || node.sourceSpan;
|
|
91
|
+
if (!startSourceSpan) {
|
|
92
|
+
startSourceSpan = nodeSourceSpan;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (nodeSourceSpan &&
|
|
96
|
+
nodeSourceSpan.start.offset < startSourceSpan.start.offset) {
|
|
97
|
+
startSourceSpan = nodeSourceSpan;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
return startSourceSpan;
|
|
102
|
+
}
|
|
103
|
+
function getEndSourceSpanFromAST(ast) {
|
|
104
|
+
let endSourceSpan = null;
|
|
105
|
+
ast.templateNodes.forEach((node) => {
|
|
106
|
+
const nodeSourceSpan = node.endSourceSpan || node.sourceSpan;
|
|
107
|
+
if (!endSourceSpan) {
|
|
108
|
+
endSourceSpan = nodeSourceSpan;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (nodeSourceSpan &&
|
|
112
|
+
nodeSourceSpan.end.offset > endSourceSpan.end.offset) {
|
|
113
|
+
endSourceSpan = nodeSourceSpan;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
return endSourceSpan;
|
|
118
|
+
}
|
|
119
|
+
function convertNgAstCommentsToTokens(comments) {
|
|
120
|
+
const commentTokens = comments.map((comment) => {
|
|
121
|
+
return {
|
|
122
|
+
// In an HTML context, effectively all our comments are Block comments
|
|
123
|
+
type: 'Block',
|
|
124
|
+
value: comment.value,
|
|
125
|
+
loc: (0, convert_source_span_to_loc_1.convertNodeSourceSpanToLoc)(comment.sourceSpan),
|
|
126
|
+
range: [comment.sourceSpan.start.offset, comment.sourceSpan.end.offset],
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
/**
|
|
130
|
+
* ESLint requires this to be sorted by Token#range[0]
|
|
131
|
+
* https://eslint.org/docs/developer-guide/working-with-custom-parsers
|
|
132
|
+
*/
|
|
133
|
+
return commentTokens.sort((a, b) => a.range[0] - b.range[0]);
|
|
134
|
+
}
|
|
135
|
+
class TemplateParseError extends Error {
|
|
136
|
+
constructor(message, fileName, index, lineNumber, column) {
|
|
137
|
+
super(message);
|
|
138
|
+
this.fileName = fileName;
|
|
139
|
+
this.index = index;
|
|
140
|
+
this.lineNumber = lineNumber;
|
|
141
|
+
this.column = column;
|
|
142
|
+
Object.defineProperty(this, 'name', {
|
|
143
|
+
value: new.target.name,
|
|
144
|
+
enumerable: false,
|
|
145
|
+
configurable: true,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
exports.TemplateParseError = TemplateParseError;
|
|
150
|
+
function createTemplateParseError(parseError) {
|
|
151
|
+
const message = parseError.msg;
|
|
152
|
+
const fileName = parseError.span.start.file.url;
|
|
153
|
+
const index = parseError.span.start.offset;
|
|
154
|
+
const lineNumber = parseError.span.start.line + 1;
|
|
155
|
+
const column = parseError.span.start.col + 1;
|
|
156
|
+
return new TemplateParseError(message, fileName, index, lineNumber, column);
|
|
157
|
+
}
|
|
158
|
+
exports.createTemplateParseError = createTemplateParseError;
|
|
159
|
+
function parseForESLint(code, options) {
|
|
160
|
+
var _a, _b;
|
|
161
|
+
const angularCompilerResult = (0, bundled_angular_compiler_1.parseTemplate)(code, options.filePath, {
|
|
162
|
+
preserveWhitespaces: true,
|
|
163
|
+
preserveLineEndings: true,
|
|
164
|
+
collectCommentNodes: true,
|
|
165
|
+
});
|
|
166
|
+
let ngAstCommentNodes = [];
|
|
167
|
+
if (Array.isArray(angularCompilerResult.commentNodes)) {
|
|
168
|
+
ngAstCommentNodes = angularCompilerResult.commentNodes;
|
|
169
|
+
}
|
|
170
|
+
const ast = {
|
|
171
|
+
type: 'Program',
|
|
172
|
+
comments: convertNgAstCommentsToTokens(ngAstCommentNodes),
|
|
173
|
+
tokens: [],
|
|
174
|
+
range: [0, 0],
|
|
175
|
+
loc: {
|
|
176
|
+
start: { line: 0, column: 0 },
|
|
177
|
+
end: { line: 0, column: 0 },
|
|
178
|
+
},
|
|
179
|
+
templateNodes: angularCompilerResult.nodes,
|
|
180
|
+
value: code,
|
|
181
|
+
};
|
|
182
|
+
// @ts-expect-error The types for ScopeManager seem to be wrong, it requires a configuration object or it will throw at runtime
|
|
183
|
+
const scopeManager = new eslint_scope_1.ScopeManager({});
|
|
184
|
+
// @ts-expect-error Create a global scope for the ScopeManager, the types for Scope also seem to be wrong
|
|
185
|
+
new eslint_scope_1.Scope(scopeManager, 'module', null, ast, false);
|
|
186
|
+
preprocessNode(ast);
|
|
187
|
+
const startSourceSpan = getStartSourceSpanFromAST(ast);
|
|
188
|
+
const endSourceSpan = getEndSourceSpanFromAST(ast);
|
|
189
|
+
if (startSourceSpan && endSourceSpan) {
|
|
190
|
+
ast.range = [startSourceSpan.start.offset, endSourceSpan.end.offset];
|
|
191
|
+
ast.loc = {
|
|
192
|
+
start: (0, convert_source_span_to_loc_1.convertNodeSourceSpanToLoc)(startSourceSpan).start,
|
|
193
|
+
end: (0, convert_source_span_to_loc_1.convertNodeSourceSpanToLoc)(endSourceSpan).end,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
// TODO: Stop suppressing parse errors by default in v14
|
|
197
|
+
const suppressParseErrors = (_a = options.suppressParseErrors) !== null && _a !== void 0 ? _a : true;
|
|
198
|
+
if (!suppressParseErrors && ((_b = angularCompilerResult.errors) === null || _b === void 0 ? void 0 : _b.length)) {
|
|
199
|
+
throw createTemplateParseError(angularCompilerResult.errors[0]);
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
ast,
|
|
203
|
+
scopeManager,
|
|
204
|
+
visitorKeys: KEYS,
|
|
205
|
+
services: {
|
|
206
|
+
convertNodeSourceSpanToLoc: convert_source_span_to_loc_1.convertNodeSourceSpanToLoc,
|
|
207
|
+
convertElementSourceSpanToLoc: convert_source_span_to_loc_1.convertElementSourceSpanToLoc,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
exports.parseForESLint = parseForESLint;
|
|
212
|
+
function parse(code, options) {
|
|
213
|
+
return parseForESLint(code, options).ast;
|
|
214
|
+
}
|
|
215
|
+
exports.parse = parse;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-eslint/template-parser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "16.0.0-alpha.1",
|
|
4
4
|
"description": "Angular Template parser for ESLint",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
"LICENSE"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@angular-eslint/bundled-angular-compiler": "
|
|
20
|
+
"@angular-eslint/bundled-angular-compiler": "16.0.0-alpha.1",
|
|
21
21
|
"eslint-scope": "^7.0.0"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"eslint": "^7.20.0 || ^8.0.0",
|
|
25
25
|
"typescript": "*"
|
|
26
26
|
},
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "ffcbae10749bc12760a9d442d4293179c6c19042"
|
|
28
28
|
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { ParseSourceSpan, TmplAstElement } from '@angular-eslint/bundled-angular-compiler';
|
|
2
|
-
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
|
|
3
|
-
export declare function convertNodeSourceSpanToLoc(sourceSpan: ParseSourceSpan): TSESTree.SourceLocation;
|
|
4
|
-
export declare function convertElementSourceSpanToLoc(context: Readonly<TSESLint.RuleContext<string, readonly unknown[]>>, node: TmplAstElement & {
|
|
5
|
-
type: string;
|
|
6
|
-
}): TSESTree.SourceLocation;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { ParseError } from '@angular-eslint/bundled-angular-compiler';
|
|
2
|
-
import type { TSESTree } from '@typescript-eslint/types';
|
|
3
|
-
import { ScopeManager } from 'eslint-scope';
|
|
4
|
-
import { convertElementSourceSpanToLoc, convertNodeSourceSpanToLoc } from './convert-source-span-to-loc';
|
|
5
|
-
type NodeOrTokenType = any;
|
|
6
|
-
interface Node {
|
|
7
|
-
[key: string]: any;
|
|
8
|
-
type: NodeOrTokenType;
|
|
9
|
-
}
|
|
10
|
-
interface VisitorKeys {
|
|
11
|
-
[nodeName: string]: string[];
|
|
12
|
-
}
|
|
13
|
-
interface Token extends TSESTree.BaseNode {
|
|
14
|
-
type: NodeOrTokenType;
|
|
15
|
-
value: string;
|
|
16
|
-
}
|
|
17
|
-
interface AST extends Node, Token {
|
|
18
|
-
comments: Token[];
|
|
19
|
-
tokens: Token[];
|
|
20
|
-
templateNodes: any[];
|
|
21
|
-
}
|
|
22
|
-
export declare class TemplateParseError extends Error {
|
|
23
|
-
readonly fileName: string;
|
|
24
|
-
readonly index: number;
|
|
25
|
-
readonly lineNumber: number;
|
|
26
|
-
readonly column: number;
|
|
27
|
-
constructor(message: string, fileName: string, index: number, lineNumber: number, column: number);
|
|
28
|
-
}
|
|
29
|
-
export declare function createTemplateParseError(parseError: ParseError): TemplateParseError;
|
|
30
|
-
export interface ParserOptions {
|
|
31
|
-
filePath: string;
|
|
32
|
-
suppressParseErrors?: boolean;
|
|
33
|
-
}
|
|
34
|
-
declare function parseForESLint(code: string, options: ParserOptions): {
|
|
35
|
-
ast: AST;
|
|
36
|
-
scopeManager: ScopeManager;
|
|
37
|
-
visitorKeys: VisitorKeys;
|
|
38
|
-
services: {
|
|
39
|
-
convertElementSourceSpanToLoc: typeof convertElementSourceSpanToLoc;
|
|
40
|
-
convertNodeSourceSpanToLoc: typeof convertNodeSourceSpanToLoc;
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
export { parseForESLint };
|
|
44
|
-
export declare function parse(code: string, options: ParserOptions): AST;
|