astro-eslint-parser 0.0.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/LICENSE +21 -0
- package/README.md +154 -0
- package/lib/ast.d.ts +42 -0
- package/lib/ast.js +2 -0
- package/lib/astro/index.d.ts +33 -0
- package/lib/astro/index.js +188 -0
- package/lib/context/index.d.ts +48 -0
- package/lib/context/index.js +201 -0
- package/lib/context/script.d.ts +29 -0
- package/lib/context/script.js +348 -0
- package/lib/debug.d.ts +2 -0
- package/lib/debug.js +8 -0
- package/lib/errors.d.ts +13 -0
- package/lib/errors.js +19 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.js +38 -0
- package/lib/parser/astro-parser/astrojs-compiler-service.d.ts +5 -0
- package/lib/parser/astro-parser/astrojs-compiler-service.js +22 -0
- package/lib/parser/astro-parser/parse.d.ts +5 -0
- package/lib/parser/astro-parser/parse.js +167 -0
- package/lib/parser/astro-parser/wasm_exec.d.ts +35 -0
- package/lib/parser/astro-parser/wasm_exec.js +470 -0
- package/lib/parser/espree.d.ts +6 -0
- package/lib/parser/espree.js +56 -0
- package/lib/parser/index.d.ts +34 -0
- package/lib/parser/index.js +96 -0
- package/lib/parser/resolve-parser.d.ts +16 -0
- package/lib/parser/resolve-parser.js +30 -0
- package/lib/parser/script.d.ts +6 -0
- package/lib/parser/script.js +41 -0
- package/lib/parser/sort.d.ts +6 -0
- package/lib/parser/sort.js +15 -0
- package/lib/traverse.d.ts +27 -0
- package/lib/traverse.js +93 -0
- package/lib/visitor-keys.d.ts +2 -0
- package/lib/visitor-keys.js +13 -0
- package/package.json +91 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Yosuke Ota
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# astro-eslint-parser
|
|
2
|
+
|
|
3
|
+
[Astro] parser for [ESLint].
|
|
4
|
+
You can check it on [Online DEMO](https://ota-meshi.github.io/astro-eslint-parser/playground).
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/astro-eslint-parser)
|
|
7
|
+
[](https://www.npmjs.com/package/astro-eslint-parser)
|
|
8
|
+
[](http://www.npmtrends.com/astro-eslint-parser)
|
|
9
|
+
[](http://www.npmtrends.com/astro-eslint-parser)
|
|
10
|
+
[](http://www.npmtrends.com/astro-eslint-parser)
|
|
11
|
+
[](http://www.npmtrends.com/astro-eslint-parser)
|
|
12
|
+
[](http://www.npmtrends.com/astro-eslint-parser)
|
|
13
|
+
[](https://github.com/ota-meshi/astro-eslint-parser/actions?query=workflow%3ACI)
|
|
14
|
+
|
|
15
|
+
This parser is in the ***experimental stages*** of development.
|
|
16
|
+
|
|
17
|
+
<!--
|
|
18
|
+
### ESLint Plugins Using astro-eslint-parser
|
|
19
|
+
|
|
20
|
+
#### [@ota-meshi/eslint-plugin-astro](https://ota-meshi.github.io/eslint-plugin-astro/)
|
|
21
|
+
|
|
22
|
+
ESLint plugin for Astro.
|
|
23
|
+
It provides many unique check rules by using the template AST.
|
|
24
|
+
-->
|
|
25
|
+
|
|
26
|
+
## 💿 Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install --save-dev eslint astro-eslint-parser
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 📖 Usage
|
|
33
|
+
|
|
34
|
+
1. Write `overrides.parser` option into your `.eslintrc.*` file.
|
|
35
|
+
2. Use glob patterns or `--ext .astro` CLI option.
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"extends": "eslint:recommended",
|
|
40
|
+
"overrides": [
|
|
41
|
+
{
|
|
42
|
+
"files": ["*.astro"],
|
|
43
|
+
"parser": "astro-eslint-parser"
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```console
|
|
50
|
+
$ eslint "src/**/*.{js,astro}"
|
|
51
|
+
# or
|
|
52
|
+
$ eslint src --ext .astro
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 🔧 Options
|
|
56
|
+
|
|
57
|
+
`parserOptions` has the same properties as what [espree](https://github.com/eslint/espree#usage), the default parser of ESLint, is supporting.
|
|
58
|
+
For example:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"parser": "astro-eslint-parser",
|
|
63
|
+
"parserOptions": {
|
|
64
|
+
"sourceType": "module",
|
|
65
|
+
"ecmaVersion": 2021,
|
|
66
|
+
"ecmaFeatures": {
|
|
67
|
+
"globalReturn": false,
|
|
68
|
+
"impliedStrict": false,
|
|
69
|
+
"jsx": false
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### parserOptions.parser
|
|
76
|
+
|
|
77
|
+
You can use `parserOptions.parser` property to specify a custom parser to parse `<script>` tags.
|
|
78
|
+
Other properties than parser would be given to the specified parser.
|
|
79
|
+
For example:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"parser": "astro-eslint-parser",
|
|
84
|
+
"parserOptions": {
|
|
85
|
+
"parser": "@typescript-eslint/parser"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
For example, if you are using the `"@typescript-eslint/parser"`, and if you want to use TypeScript in `<script>` of `.astro`, you need to add more `parserOptions` configuration.
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
module.exports = {
|
|
94
|
+
// ...
|
|
95
|
+
parser: "@typescript-eslint/parser",
|
|
96
|
+
parserOptions: {
|
|
97
|
+
// ...
|
|
98
|
+
project: "path/to/your/tsconfig.json",
|
|
99
|
+
extraFileExtensions: [".astro"], // This is a required setting in `@typescript-eslint/parser` v4.24.0.
|
|
100
|
+
},
|
|
101
|
+
overrides: [
|
|
102
|
+
{
|
|
103
|
+
files: ["*.astro"],
|
|
104
|
+
parser: "astro-eslint-parser",
|
|
105
|
+
// Parse the `<script>` in `.astro` as TypeScript by adding the following configuration.
|
|
106
|
+
parserOptions: {
|
|
107
|
+
parser: "@typescript-eslint/parser",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
// ...
|
|
111
|
+
],
|
|
112
|
+
// ...
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## :computer: Editor Integrations
|
|
117
|
+
|
|
118
|
+
### Visual Studio Code
|
|
119
|
+
|
|
120
|
+
Use the [dbaeumer.vscode-eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) extension that Microsoft provides officially.
|
|
121
|
+
|
|
122
|
+
You have to configure the `eslint.validate` option of the extension to check `.astro` files, because the extension targets only `*.js` or `*.jsx` files by default.
|
|
123
|
+
|
|
124
|
+
Example **.vscode/settings.json**:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"eslint.validate": [
|
|
129
|
+
"javascript",
|
|
130
|
+
"javascriptreact",
|
|
131
|
+
"astro"
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Usage for Custom Rules / Plugins
|
|
137
|
+
|
|
138
|
+
- TBD
|
|
139
|
+
|
|
140
|
+
<!-- - [AST.md](./docs/AST.md) is AST specification. You can check it on the [Online DEMO](https://ota-meshi.github.io/astro-eslint-parser/). -->
|
|
141
|
+
<!-- - I have already [implemented some rules] in the [`@ota-meshi/eslint-plugin-astro`]. The source code for these rules will be helpful to you. -->
|
|
142
|
+
|
|
143
|
+
## :beers: Contributing
|
|
144
|
+
|
|
145
|
+
Welcome contributing!
|
|
146
|
+
|
|
147
|
+
Please use GitHub's Issues/PRs.
|
|
148
|
+
|
|
149
|
+
## :lock: License
|
|
150
|
+
|
|
151
|
+
See the [LICENSE](LICENSE) file for license rights and limitations (MIT).
|
|
152
|
+
|
|
153
|
+
[Astro]: https://astro.build/
|
|
154
|
+
[ESLint]: https://eslint.org/
|
package/lib/ast.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { TSESTree } from "@typescript-eslint/types";
|
|
2
|
+
export declare type AstroNode = AstroProgram | AstroRootFragment | AstroHTMLComment | AstroDoctype | AstroShorthandAttribute | AstroTemplateLiteralAttribute;
|
|
3
|
+
/** Node of Astro program root */
|
|
4
|
+
export interface AstroProgram extends Omit<TSESTree.Program, "type" | "body"> {
|
|
5
|
+
type: "Program";
|
|
6
|
+
body: (TSESTree.Program["body"][number] | AstroRootFragment | AstroHTMLComment)[];
|
|
7
|
+
sourceType: "script" | "module";
|
|
8
|
+
comments: TSESTree.Comment[];
|
|
9
|
+
tokens: TSESTree.Token[];
|
|
10
|
+
parent: undefined;
|
|
11
|
+
}
|
|
12
|
+
/** Node of Astro fragment root */
|
|
13
|
+
export interface AstroRootFragment extends Omit<TSESTree.BaseNode, "type" | "parent"> {
|
|
14
|
+
type: "AstroRootFragment";
|
|
15
|
+
children: TSESTree.JSXFragment["children"];
|
|
16
|
+
parent: AstroProgram;
|
|
17
|
+
}
|
|
18
|
+
/** Node of Astro html comment */
|
|
19
|
+
export interface AstroHTMLComment extends Omit<TSESTree.BaseNode, "type" | "parent"> {
|
|
20
|
+
type: "AstroHTMLComment";
|
|
21
|
+
value: string;
|
|
22
|
+
parent: AstroRootFragment | TSESTree.JSXElement | TSESTree.JSXFragment;
|
|
23
|
+
}
|
|
24
|
+
/** Node of Astro doctype */
|
|
25
|
+
export interface AstroDoctype extends Omit<TSESTree.BaseNode, "type" | "parent"> {
|
|
26
|
+
type: "AstroDoctype";
|
|
27
|
+
parent: AstroRootFragment;
|
|
28
|
+
}
|
|
29
|
+
/** Node of Astro shorthand attribute */
|
|
30
|
+
export interface AstroShorthandAttribute extends Omit<TSESTree.JSXAttribute, "type" | "parent"> {
|
|
31
|
+
type: "AstroShorthandAttribute";
|
|
32
|
+
value: TSESTree.JSXExpressionContainer;
|
|
33
|
+
parent: TSESTree.JSXElement | TSESTree.JSXFragment;
|
|
34
|
+
}
|
|
35
|
+
/** Node of Astro template-literal attribute */
|
|
36
|
+
export interface AstroTemplateLiteralAttribute extends Omit<TSESTree.JSXAttribute, "type" | "parent"> {
|
|
37
|
+
type: "AstroTemplateLiteralAttribute";
|
|
38
|
+
value: TSESTree.JSXExpressionContainer & {
|
|
39
|
+
expression: TSESTree.TemplateLiteral;
|
|
40
|
+
};
|
|
41
|
+
parent: TSESTree.JSXElement | TSESTree.JSXFragment;
|
|
42
|
+
}
|
package/lib/ast.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AttributeNode, CommentNode, Node, ParentNode, TagLikeNode } from "@astrojs/compiler/types";
|
|
2
|
+
/**
|
|
3
|
+
* Checks if the given node is TagLikeNode
|
|
4
|
+
*/
|
|
5
|
+
export declare function isTag(node: Node): node is Node & TagLikeNode;
|
|
6
|
+
/**
|
|
7
|
+
* Checks if the given node is ParentNode
|
|
8
|
+
*/
|
|
9
|
+
export declare function isParent(node: Node): node is ParentNode;
|
|
10
|
+
/** walk element nodes */
|
|
11
|
+
export declare function walkElements(parent: ParentNode, cb: (n: Node, parent: ParentNode) => void): void;
|
|
12
|
+
/** walk nodes */
|
|
13
|
+
export declare function walk(parent: ParentNode, enter: (n: Node | AttributeNode, parent: ParentNode) => void, leave?: (n: Node | AttributeNode, parent: ParentNode) => void): void;
|
|
14
|
+
/**
|
|
15
|
+
* Get end offset of start tag
|
|
16
|
+
*/
|
|
17
|
+
export declare function getStartTagEndOffset(node: TagLikeNode, code: string): number;
|
|
18
|
+
/**
|
|
19
|
+
* Get end offset of attribute
|
|
20
|
+
*/
|
|
21
|
+
export declare function getAttributeEndOffset(node: AttributeNode, code: string): number;
|
|
22
|
+
/**
|
|
23
|
+
* Get start offset of attribute value
|
|
24
|
+
*/
|
|
25
|
+
export declare function getAttributeValueStartOffset(node: AttributeNode, code: string): number;
|
|
26
|
+
/**
|
|
27
|
+
* Get end offset of comment
|
|
28
|
+
*/
|
|
29
|
+
export declare function getCommentEndOffset(node: CommentNode, code: string): number;
|
|
30
|
+
/**
|
|
31
|
+
* Skip spaces
|
|
32
|
+
*/
|
|
33
|
+
export declare function skipSpaces(string: string, position: number): number;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.skipSpaces = exports.getCommentEndOffset = exports.getAttributeValueStartOffset = exports.getAttributeEndOffset = exports.getStartTagEndOffset = exports.walk = exports.walkElements = exports.isParent = exports.isTag = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Checks if the given node is TagLikeNode
|
|
6
|
+
*/
|
|
7
|
+
function isTag(node) {
|
|
8
|
+
return (node.type === "element" ||
|
|
9
|
+
node.type === "custom-element" ||
|
|
10
|
+
node.type === "component" ||
|
|
11
|
+
node.type === "fragment");
|
|
12
|
+
}
|
|
13
|
+
exports.isTag = isTag;
|
|
14
|
+
/**
|
|
15
|
+
* Checks if the given node is ParentNode
|
|
16
|
+
*/
|
|
17
|
+
function isParent(node) {
|
|
18
|
+
return Array.isArray(node.children);
|
|
19
|
+
}
|
|
20
|
+
exports.isParent = isParent;
|
|
21
|
+
/** walk element nodes */
|
|
22
|
+
function walkElements(parent, cb) {
|
|
23
|
+
let children = parent.children;
|
|
24
|
+
if (parent.type === "root" && children.every((n) => n.position)) {
|
|
25
|
+
// The order of comments and frontmatter may be changed.
|
|
26
|
+
children = [...children].sort((a, b) => a.position.start.offset - b.position.start.offset);
|
|
27
|
+
}
|
|
28
|
+
for (const node of children) {
|
|
29
|
+
cb(node, parent);
|
|
30
|
+
if (isParent(node)) {
|
|
31
|
+
walkElements(node, cb);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.walkElements = walkElements;
|
|
36
|
+
/** walk nodes */
|
|
37
|
+
function walk(parent, enter, leave) {
|
|
38
|
+
let children = parent.children;
|
|
39
|
+
if (parent.type === "root" && children.every((n) => n.position)) {
|
|
40
|
+
// The order of comments and frontmatter may be changed.
|
|
41
|
+
children = [...children].sort((a, b) => a.position.start.offset - b.position.start.offset);
|
|
42
|
+
}
|
|
43
|
+
for (const node of children) {
|
|
44
|
+
enter(node, parent);
|
|
45
|
+
if (isTag(node)) {
|
|
46
|
+
for (const attr of node.attributes) {
|
|
47
|
+
enter(attr, node);
|
|
48
|
+
leave === null || leave === void 0 ? void 0 : leave(attr, node);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (isParent(node)) {
|
|
52
|
+
walk(node, enter, leave);
|
|
53
|
+
}
|
|
54
|
+
leave === null || leave === void 0 ? void 0 : leave(node, parent);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.walk = walk;
|
|
58
|
+
/**
|
|
59
|
+
* Get end offset of start tag
|
|
60
|
+
*/
|
|
61
|
+
function getStartTagEndOffset(node, code) {
|
|
62
|
+
const lastAttr = node.attributes[node.attributes.length - 1];
|
|
63
|
+
let beforeCloseIndex;
|
|
64
|
+
if (lastAttr) {
|
|
65
|
+
beforeCloseIndex = getAttributeEndOffset(lastAttr, code);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const info = getTokenInfo(code, [`<${node.name}`], node.position.start.offset);
|
|
69
|
+
beforeCloseIndex = info.index + info.match.length;
|
|
70
|
+
}
|
|
71
|
+
const info = getTokenInfo(code, [[">", "/>"]], beforeCloseIndex);
|
|
72
|
+
return info.index + info.match.length;
|
|
73
|
+
}
|
|
74
|
+
exports.getStartTagEndOffset = getStartTagEndOffset;
|
|
75
|
+
/**
|
|
76
|
+
* Get end offset of attribute
|
|
77
|
+
*/
|
|
78
|
+
function getAttributeEndOffset(node, code) {
|
|
79
|
+
let info;
|
|
80
|
+
if (node.kind === "empty") {
|
|
81
|
+
info = getTokenInfo(code, [node.name], node.position.start.offset);
|
|
82
|
+
}
|
|
83
|
+
else if (node.kind === "quoted") {
|
|
84
|
+
info = getTokenInfo(code, [[`"${node.value}"`, `'${node.value}'`, node.value]], getAttributeValueStartOffset(node, code));
|
|
85
|
+
}
|
|
86
|
+
else if (node.kind === "expression") {
|
|
87
|
+
info = getTokenInfo(code, ["{", node.value, "}"], getAttributeValueStartOffset(node, code));
|
|
88
|
+
}
|
|
89
|
+
else if (node.kind === "shorthand") {
|
|
90
|
+
info = getTokenInfo(code, ["{", node.name, "}"], node.position.start.offset);
|
|
91
|
+
}
|
|
92
|
+
else if (node.kind === "spread") {
|
|
93
|
+
info = getTokenInfo(code, ["{", "...", node.name, "}"], node.position.start.offset);
|
|
94
|
+
}
|
|
95
|
+
else if (node.kind === "template-literal") {
|
|
96
|
+
info = getTokenInfo(code, [`\`${node.value}\``], getAttributeValueStartOffset(node, code));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
throw new Error(`Unknown attr kind: ${node.kind}`);
|
|
100
|
+
}
|
|
101
|
+
return info.index + info.match.length;
|
|
102
|
+
}
|
|
103
|
+
exports.getAttributeEndOffset = getAttributeEndOffset;
|
|
104
|
+
/**
|
|
105
|
+
* Get start offset of attribute value
|
|
106
|
+
*/
|
|
107
|
+
function getAttributeValueStartOffset(node, code) {
|
|
108
|
+
let info;
|
|
109
|
+
if (node.kind === "quoted") {
|
|
110
|
+
info = getTokenInfo(code, [node.name, "=", [`"`, `'`, node.value]], node.position.start.offset);
|
|
111
|
+
}
|
|
112
|
+
else if (node.kind === "expression") {
|
|
113
|
+
info = getTokenInfo(code, [node.name, "=", "{"], node.position.start.offset);
|
|
114
|
+
}
|
|
115
|
+
else if (node.kind === "template-literal") {
|
|
116
|
+
info = getTokenInfo(code, [node.name, "=", "`"], node.position.start.offset);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
throw new Error(`Unknown attr kind: ${node.kind}`);
|
|
120
|
+
}
|
|
121
|
+
return info.index;
|
|
122
|
+
}
|
|
123
|
+
exports.getAttributeValueStartOffset = getAttributeValueStartOffset;
|
|
124
|
+
/**
|
|
125
|
+
* Get end offset of comment
|
|
126
|
+
*/
|
|
127
|
+
function getCommentEndOffset(node, code) {
|
|
128
|
+
const info = getTokenInfo(code, ["<!--", node.value, "-->"], node.position.start.offset);
|
|
129
|
+
return info.index + info.match.length;
|
|
130
|
+
}
|
|
131
|
+
exports.getCommentEndOffset = getCommentEndOffset;
|
|
132
|
+
/**
|
|
133
|
+
* Get token info
|
|
134
|
+
*/
|
|
135
|
+
function getTokenInfo(string, tokens, position) {
|
|
136
|
+
let lastMatch;
|
|
137
|
+
for (const t of tokens) {
|
|
138
|
+
const index = lastMatch
|
|
139
|
+
? lastMatch.index + lastMatch.match.length
|
|
140
|
+
: position;
|
|
141
|
+
const m = typeof t === "string"
|
|
142
|
+
? matchOfStr(t, index)
|
|
143
|
+
: matchOfForMulti(t, index);
|
|
144
|
+
if (m == null) {
|
|
145
|
+
throw new Error(`Unknown token at ${index}, expected: ${JSON.stringify(t)}, actual: ${JSON.stringify(string.slice(index, index + 10))}`);
|
|
146
|
+
}
|
|
147
|
+
lastMatch = m;
|
|
148
|
+
}
|
|
149
|
+
return lastMatch;
|
|
150
|
+
/**
|
|
151
|
+
* For string
|
|
152
|
+
*/
|
|
153
|
+
function matchOfStr(search, position) {
|
|
154
|
+
const index = search.trim() === search ? skipSpaces(string, position) : position;
|
|
155
|
+
if (string.startsWith(search, index)) {
|
|
156
|
+
return {
|
|
157
|
+
match: search,
|
|
158
|
+
index,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* For multi
|
|
165
|
+
*/
|
|
166
|
+
function matchOfForMulti(search, position) {
|
|
167
|
+
for (const s of search) {
|
|
168
|
+
const m = matchOfStr(s, position);
|
|
169
|
+
if (m) {
|
|
170
|
+
return m;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Skip spaces
|
|
178
|
+
*/
|
|
179
|
+
function skipSpaces(string, position) {
|
|
180
|
+
const re = /\s*/g;
|
|
181
|
+
re.lastIndex = position;
|
|
182
|
+
const match = re.exec(string);
|
|
183
|
+
if (match) {
|
|
184
|
+
return match.index + match[0].length;
|
|
185
|
+
}
|
|
186
|
+
return position;
|
|
187
|
+
}
|
|
188
|
+
exports.skipSpaces = skipSpaces;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { TSESTree } from "@typescript-eslint/types";
|
|
2
|
+
import type { ESLintExtendedProgram } from "../parser";
|
|
3
|
+
declare type RangeAndLoc = {
|
|
4
|
+
range: TSESTree.Range;
|
|
5
|
+
loc: TSESTree.SourceLocation;
|
|
6
|
+
};
|
|
7
|
+
export declare class Context {
|
|
8
|
+
readonly code: string;
|
|
9
|
+
readonly parserOptions: any;
|
|
10
|
+
readonly locs: LinesAndColumns;
|
|
11
|
+
private readonly locsMap;
|
|
12
|
+
private state;
|
|
13
|
+
constructor(code: string, parserOptions: any);
|
|
14
|
+
getLocFromIndex(index: number): {
|
|
15
|
+
line: number;
|
|
16
|
+
column: number;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Get the location information of the given indexes.
|
|
20
|
+
*/
|
|
21
|
+
getLocations(start: number, end: number): RangeAndLoc;
|
|
22
|
+
/**
|
|
23
|
+
* Build token
|
|
24
|
+
*/
|
|
25
|
+
buildToken(type: TSESTree.Token["type"], range: TSESTree.Range): TSESTree.Token;
|
|
26
|
+
/**
|
|
27
|
+
* get text
|
|
28
|
+
*/
|
|
29
|
+
getText(range: TSESTree.Range): string;
|
|
30
|
+
isTypeScript(): boolean;
|
|
31
|
+
remapCR({ ast, visitorKeys }: ESLintExtendedProgram): void;
|
|
32
|
+
}
|
|
33
|
+
export declare class LinesAndColumns {
|
|
34
|
+
readonly code: string;
|
|
35
|
+
private readonly crs;
|
|
36
|
+
private readonly lineStartIndices;
|
|
37
|
+
constructor(origCode: string);
|
|
38
|
+
getLocFromIndex(index: number): {
|
|
39
|
+
line: number;
|
|
40
|
+
column: number;
|
|
41
|
+
};
|
|
42
|
+
getIndexFromLoc(loc: {
|
|
43
|
+
line: number;
|
|
44
|
+
column: number;
|
|
45
|
+
}): number;
|
|
46
|
+
getCRs(): number[];
|
|
47
|
+
}
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LinesAndColumns = exports.Context = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const resolve_parser_1 = require("../parser/resolve-parser");
|
|
10
|
+
const traverse_1 = require("../traverse");
|
|
11
|
+
class Context {
|
|
12
|
+
constructor(code, parserOptions) {
|
|
13
|
+
this.locsMap = new Map();
|
|
14
|
+
this.state = {};
|
|
15
|
+
this.locs = new LinesAndColumns(code);
|
|
16
|
+
this.code = this.locs.code;
|
|
17
|
+
this.parserOptions = parserOptions;
|
|
18
|
+
}
|
|
19
|
+
getLocFromIndex(index) {
|
|
20
|
+
let loc = this.locsMap.get(index);
|
|
21
|
+
if (!loc) {
|
|
22
|
+
loc = this.locs.getLocFromIndex(index);
|
|
23
|
+
this.locsMap.set(index, loc);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
line: loc.line,
|
|
27
|
+
column: loc.column,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get the location information of the given indexes.
|
|
32
|
+
*/
|
|
33
|
+
getLocations(start, end) {
|
|
34
|
+
return {
|
|
35
|
+
range: [start, end],
|
|
36
|
+
loc: {
|
|
37
|
+
start: this.getLocFromIndex(start),
|
|
38
|
+
end: this.getLocFromIndex(end),
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build token
|
|
44
|
+
*/
|
|
45
|
+
buildToken(type, range) {
|
|
46
|
+
return Object.assign({ type, value: this.getText(range) }, this.getLocations(...range));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* get text
|
|
50
|
+
*/
|
|
51
|
+
getText(range) {
|
|
52
|
+
return this.code.slice(range[0], range[1]);
|
|
53
|
+
}
|
|
54
|
+
isTypeScript() {
|
|
55
|
+
var _a, _b;
|
|
56
|
+
if (this.state.isTypeScript != null) {
|
|
57
|
+
return this.state.isTypeScript;
|
|
58
|
+
}
|
|
59
|
+
const parserName = (0, resolve_parser_1.getParserName)({}, (_a = this.parserOptions) === null || _a === void 0 ? void 0 : _a.parser);
|
|
60
|
+
if (parserName === "@typescript-eslint/parser") {
|
|
61
|
+
return (this.state.isTypeScript = true);
|
|
62
|
+
}
|
|
63
|
+
if (parserName.includes("@typescript-eslint/parser")) {
|
|
64
|
+
let targetPath = parserName;
|
|
65
|
+
while (targetPath) {
|
|
66
|
+
const pkgPath = path_1.default.join(targetPath, "package.json");
|
|
67
|
+
if (fs_1.default.existsSync(pkgPath)) {
|
|
68
|
+
try {
|
|
69
|
+
return (this.state.isTypeScript =
|
|
70
|
+
((_b = JSON.parse(fs_1.default.readFileSync(pkgPath, "utf-8"))) === null || _b === void 0 ? void 0 : _b.name) === "@typescript-eslint/parser");
|
|
71
|
+
}
|
|
72
|
+
catch (_c) {
|
|
73
|
+
return (this.state.isTypeScript = false);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const parent = path_1.default.dirname(targetPath);
|
|
77
|
+
if (targetPath === parent) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
targetPath = parent;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return (this.state.isTypeScript = false);
|
|
84
|
+
}
|
|
85
|
+
remapCR({ ast, visitorKeys }) {
|
|
86
|
+
const crs = this.locs.getCRs();
|
|
87
|
+
if (!crs.length) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const cache = {};
|
|
91
|
+
/**
|
|
92
|
+
* Remap index
|
|
93
|
+
*/
|
|
94
|
+
function remapIndex(index) {
|
|
95
|
+
let result = cache[index];
|
|
96
|
+
if (result != null) {
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
result = index;
|
|
100
|
+
for (const cr of crs) {
|
|
101
|
+
if (cr < result) {
|
|
102
|
+
result++;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return (cache[index] = result);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Remap range
|
|
112
|
+
*/
|
|
113
|
+
function remapRange(range) {
|
|
114
|
+
return [remapIndex(range[0]), remapIndex(range[1])];
|
|
115
|
+
}
|
|
116
|
+
(0, traverse_1.traverseNodes)(ast, {
|
|
117
|
+
visitorKeys,
|
|
118
|
+
enterNode(node) {
|
|
119
|
+
node.range = remapRange(node.range);
|
|
120
|
+
},
|
|
121
|
+
leaveNode() {
|
|
122
|
+
// ignore
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
for (const token of ast.tokens || []) {
|
|
126
|
+
token.range = remapRange(token.range);
|
|
127
|
+
}
|
|
128
|
+
for (const comment of ast.comments || []) {
|
|
129
|
+
comment.range = remapRange(comment.range);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.Context = Context;
|
|
134
|
+
class LinesAndColumns {
|
|
135
|
+
constructor(origCode) {
|
|
136
|
+
const len = origCode.length;
|
|
137
|
+
const lineStartIndices = [0];
|
|
138
|
+
const crs = [];
|
|
139
|
+
let code = "";
|
|
140
|
+
for (let index = 0; index < len;) {
|
|
141
|
+
const c = origCode[index++];
|
|
142
|
+
if (c === "\r") {
|
|
143
|
+
const next = origCode[index++] || "";
|
|
144
|
+
if (next === "\n") {
|
|
145
|
+
code += next;
|
|
146
|
+
crs.push(index - 2);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
code += `\n${next}`;
|
|
150
|
+
}
|
|
151
|
+
lineStartIndices.push(code.length);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
code += c;
|
|
155
|
+
if (c === "\n") {
|
|
156
|
+
lineStartIndices.push(code.length);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
this.lineStartIndices = lineStartIndices;
|
|
161
|
+
this.code = code;
|
|
162
|
+
this.crs = crs;
|
|
163
|
+
}
|
|
164
|
+
getLocFromIndex(index) {
|
|
165
|
+
const lineNumber = sortedLastIndex(this.lineStartIndices, index);
|
|
166
|
+
return {
|
|
167
|
+
line: lineNumber,
|
|
168
|
+
column: index - this.lineStartIndices[lineNumber - 1],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
getIndexFromLoc(loc) {
|
|
172
|
+
const lineStartIndex = this.lineStartIndices[loc.line - 1];
|
|
173
|
+
const positionIndex = lineStartIndex + loc.column;
|
|
174
|
+
return positionIndex;
|
|
175
|
+
}
|
|
176
|
+
getCRs() {
|
|
177
|
+
return this.crs;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
exports.LinesAndColumns = LinesAndColumns;
|
|
181
|
+
/**
|
|
182
|
+
* Uses a binary search to determine the highest index at which value should be inserted into array in order to maintain its sort order.
|
|
183
|
+
*/
|
|
184
|
+
function sortedLastIndex(array, value) {
|
|
185
|
+
let lower = 0;
|
|
186
|
+
let upper = array.length;
|
|
187
|
+
while (lower < upper) {
|
|
188
|
+
const mid = Math.floor(lower + (upper - lower) / 2);
|
|
189
|
+
const target = array[mid];
|
|
190
|
+
if (target < value) {
|
|
191
|
+
lower = mid + 1;
|
|
192
|
+
}
|
|
193
|
+
else if (target > value) {
|
|
194
|
+
upper = mid;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
return mid + 1;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return upper;
|
|
201
|
+
}
|