Skip to content

Commit eee33a6

Browse files
authored
Update (#2)
* Update * update * Update * fix testcase
1 parent 26a579f commit eee33a6

File tree

118 files changed

+284002
-314
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+284002
-314
lines changed

.eslintrc-for-playground.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ module.exports = {
44
extends: [
55
require.resolve("./.eslintrc.js"),
66
],
7+
parserOptions: {
8+
sourceType: "module",
9+
ecmaVersion: 2022,
10+
parser: "@typescript-eslint/parser",
11+
},
712
overrides: [
813
{
914
files: ["*.astro"],

README.md

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ You can check it on [Online DEMO](https://ota-meshi.github.io/astro-eslint-parse
1111
[![NPM downloads](https://img.shields.io/npm/dy/astro-eslint-parser.svg)](http://www.npmtrends.com/astro-eslint-parser)
1212
[![NPM downloads](https://img.shields.io/npm/dt/astro-eslint-parser.svg)](http://www.npmtrends.com/astro-eslint-parser)
1313
[![Build Status](https://github.com/ota-meshi/astro-eslint-parser/workflows/CI/badge.svg?branch=main)](https://github.com/ota-meshi/astro-eslint-parser/actions?query=workflow%3ACI)
14-
[![Coverage Status](https://coveralls.io/repos/github/ota-meshi/astro-eslint-parser/badge.svg?branch=main)](https://coveralls.io/github/ota-meshi/astro-eslint-parser?branch=main)
14+
15+
This parser is in the ***experimental stages*** of development.
1516

1617
<!--
1718
### ESLint Plugins Using astro-eslint-parser
@@ -112,23 +113,6 @@ module.exports = {
112113
}
113114
```
114115

115-
#### Multiple parsers
116-
117-
If you want to switch the parser for each lang, specify the object.
118-
119-
```json
120-
{
121-
"parser": "astro-eslint-parser",
122-
"parserOptions": {
123-
"parser": {
124-
"ts": "@typescript-eslint/parser",
125-
"js": "espree",
126-
"typescript": "@typescript-eslint/parser"
127-
}
128-
}
129-
}
130-
```
131-
132116
## :computer: Editor Integrations
133117

134118
### Visual Studio Code
@@ -151,8 +135,9 @@ Example **.vscode/settings.json**:
151135

152136
## Usage for Custom Rules / Plugins
153137

154-
- [AST.md](./docs/AST.md) is AST specification. You can check it on the [Online DEMO](https://ota-meshi.github.io/astro-eslint-parser/).
155-
- The parser will generate its own [ScopeManager](https://eslint.org/docs/developer-guide/scope-manager-interface). You can check it on the [Online DEMO](https://ota-meshi.github.io/astro-eslint-parser/scope).
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/). -->
156141
<!-- - I have already [implemented some rules] in the [`@ota-meshi/eslint-plugin-astro`]. The source code for these rules will be helpful to you. -->
157142

158143
## :beers: Contributing

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"homepage": "https://github.com/ota-meshi/astro-eslint-parser#readme",
4343
"dependencies": {
4444
"@astrojs/compiler": "^0.14.2",
45+
"debug": "^4.3.4",
4546
"eslint-scope": "^7.0.0",
4647
"eslint-visitor-keys": "^3.0.0",
4748
"espree": "^9.0.0"
@@ -50,6 +51,7 @@
5051
"@ota-meshi/eslint-plugin": "^0.10.0",
5152
"@types/benchmark": "^2.1.1",
5253
"@types/chai": "^4.3.0",
54+
"@types/debug": "^4.1.7",
5355
"@types/eslint": "^8.0.0",
5456
"@types/eslint-scope": "^3.7.0",
5557
"@types/eslint-visitor-keys": "^1.0.0",
@@ -63,6 +65,7 @@
6365
"code-red": "^0.2.3",
6466
"eslint": "^8.2.0",
6567
"eslint-config-prettier": "^8.3.0",
68+
"eslint-formatter-codeframe": "^7.32.1",
6669
"eslint-plugin-eslint-comments": "^3.2.0",
6770
"eslint-plugin-json-schema-validator": "^2.1.6",
6871
"eslint-plugin-jsonc": "^2.0.0",

src/ast.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ export type AstroNode =
55
| AstroRootFragment
66
| AstroHTMLComment
77
| AstroDoctype
8+
| AstroShorthandAttribute
9+
| AstroTemplateLiteralAttribute
810

911
/** Node of Astro program root */
1012
export interface AstroProgram extends Omit<TSESTree.Program, "type" | "body"> {
@@ -41,3 +43,19 @@ export interface AstroDoctype
4143
type: "AstroDoctype"
4244
parent: AstroRootFragment
4345
}
46+
/** Node of Astro shorthand attribute */
47+
export interface AstroShorthandAttribute
48+
extends Omit<TSESTree.JSXAttribute, "type" | "parent"> {
49+
type: "AstroShorthandAttribute"
50+
value: TSESTree.JSXExpressionContainer
51+
parent: TSESTree.JSXElement | TSESTree.JSXFragment
52+
}
53+
/** Node of Astro template-literal attribute */
54+
export interface AstroTemplateLiteralAttribute
55+
extends Omit<TSESTree.JSXAttribute, "type" | "parent"> {
56+
type: "AstroTemplateLiteralAttribute"
57+
value: TSESTree.JSXExpressionContainer & {
58+
expression: TSESTree.TemplateLiteral
59+
}
60+
parent: TSESTree.JSXElement | TSESTree.JSXFragment
61+
}

src/astro/index.ts

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type {
22
AttributeNode,
3+
CommentNode,
34
Node,
45
ParentNode,
56
TagLikeNode,
@@ -71,3 +72,192 @@ export function walk(
7172
leave?.(node, parent)
7273
}
7374
}
75+
76+
/**
77+
* Get end offset of start tag
78+
*/
79+
export function getStartTagEndOffset(node: TagLikeNode, code: string): number {
80+
const lastAttr = node.attributes[node.attributes.length - 1]
81+
let beforeCloseIndex: number
82+
if (lastAttr) {
83+
beforeCloseIndex = getAttributeEndOffset(lastAttr, code)
84+
} else {
85+
const info = getTokenInfo(
86+
code,
87+
[`<${node.name}`],
88+
node.position!.start.offset,
89+
)
90+
beforeCloseIndex = info.index + info.match.length
91+
}
92+
const info = getTokenInfo(code, [[">", "/>"]], beforeCloseIndex)
93+
return info.index + info.match.length
94+
}
95+
96+
/**
97+
* Get end offset of attribute
98+
*/
99+
export function getAttributeEndOffset(
100+
node: AttributeNode,
101+
code: string,
102+
): number {
103+
let info
104+
if (node.kind === "empty") {
105+
info = getTokenInfo(code, [node.name], node.position!.start.offset)
106+
} else if (node.kind === "quoted") {
107+
info = getTokenInfo(
108+
code,
109+
[[`"${node.value}"`, `'${node.value}'`, node.value]],
110+
getAttributeValueStartOffset(node, code),
111+
)
112+
} else if (node.kind === "expression") {
113+
info = getTokenInfo(
114+
code,
115+
["{", node.value, "}"],
116+
getAttributeValueStartOffset(node, code),
117+
)
118+
} else if (node.kind === "shorthand") {
119+
info = getTokenInfo(
120+
code,
121+
["{", node.name, "}"],
122+
node.position!.start.offset,
123+
)
124+
} else if (node.kind === "spread") {
125+
info = getTokenInfo(
126+
code,
127+
["{", "...", node.name, "}"],
128+
node.position!.start.offset,
129+
)
130+
} else if (node.kind === "template-literal") {
131+
info = getTokenInfo(
132+
code,
133+
[`\`${node.value}\``],
134+
getAttributeValueStartOffset(node, code),
135+
)
136+
} else {
137+
throw new Error(`Unknown attr kind: ${node.kind}`)
138+
}
139+
return info.index + info.match.length
140+
}
141+
142+
/**
143+
* Get start offset of attribute value
144+
*/
145+
export function getAttributeValueStartOffset(
146+
node: AttributeNode,
147+
code: string,
148+
): number {
149+
let info
150+
if (node.kind === "quoted") {
151+
info = getTokenInfo(
152+
code,
153+
[node.name, "=", [`"`, `'`, node.value]],
154+
node.position!.start.offset,
155+
)
156+
} else if (node.kind === "expression") {
157+
info = getTokenInfo(
158+
code,
159+
[node.name, "=", "{"],
160+
node.position!.start.offset,
161+
)
162+
} else if (node.kind === "template-literal") {
163+
info = getTokenInfo(
164+
code,
165+
[node.name, "=", "`"],
166+
node.position!.start.offset,
167+
)
168+
} else {
169+
throw new Error(`Unknown attr kind: ${node.kind}`)
170+
}
171+
return info.index
172+
}
173+
174+
/**
175+
* Get end offset of comment
176+
*/
177+
export function getCommentEndOffset(node: CommentNode, code: string): number {
178+
const info = getTokenInfo(
179+
code,
180+
["<!--", node.value, "-->"],
181+
node.position!.start.offset,
182+
)
183+
184+
return info.index + info.match.length
185+
}
186+
187+
/**
188+
* Get token info
189+
*/
190+
function getTokenInfo(
191+
string: string,
192+
tokens: (string | string[])[],
193+
position: number,
194+
): {
195+
match: string
196+
index: number
197+
} {
198+
let lastMatch:
199+
| {
200+
match: string
201+
index: number
202+
}
203+
| undefined
204+
for (const t of tokens) {
205+
const index = lastMatch
206+
? lastMatch.index + lastMatch.match.length
207+
: position
208+
const m =
209+
typeof t === "string"
210+
? matchOfStr(t, index)
211+
: matchOfForMulti(t, index)
212+
if (m == null) {
213+
throw new Error(
214+
`Unknown token at ${index}, expected: ${JSON.stringify(
215+
t,
216+
)}, actual: ${JSON.stringify(string.slice(index, index + 10))}`,
217+
)
218+
}
219+
lastMatch = m
220+
}
221+
return lastMatch!
222+
223+
/**
224+
* For string
225+
*/
226+
function matchOfStr(search: string, position: number) {
227+
const index =
228+
search.trim() === search ? skipSpaces(string, position) : position
229+
if (string.startsWith(search, index)) {
230+
return {
231+
match: search,
232+
index,
233+
}
234+
}
235+
return null
236+
}
237+
238+
/**
239+
* For multi
240+
*/
241+
function matchOfForMulti(search: string[], position: number) {
242+
for (const s of search) {
243+
const m = matchOfStr(s, position)
244+
if (m) {
245+
return m
246+
}
247+
}
248+
return null
249+
}
250+
}
251+
252+
/**
253+
* Skip spaces
254+
*/
255+
export function skipSpaces(string: string, position: number): number {
256+
const re = /\s*/g
257+
re.lastIndex = position
258+
const match = re.exec(string)
259+
if (match) {
260+
return match.index + match[0].length
261+
}
262+
return position
263+
}

0 commit comments

Comments
 (0)