Skip to content

Commit 624d26c

Browse files
authored
refactor: use AST to parse code (#5)
* chore: update deps * refactor: use ast to parse code * chore: clear test * feat: support basic if transform * feat: support vite env * feat: support loadEnv and logger * feat: support define and message * docs: update readme * test: add parser test * test: add transformer test * chore: lint code * test: add generator test * test: add more test * feat: add comment type for token * fix: support generate source comment * test: update * chore: lint code * feat: return remap for vite fixed #4 * docs: update readme
1 parent 8756d31 commit 624d26c

Some content is hidden

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

78 files changed

+2623
-2147
lines changed

.eslintignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

.eslintrc

Lines changed: 0 additions & 3 deletions
This file was deleted.

.vscode/settings.json

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,69 @@
11
{
2-
"prettier.enable": false
2+
// Enable the ESlint flat config support
3+
"eslint.experimental.useFlatConfig": true,
4+
// Disable the default formatter, use eslint instead
5+
"prettier.enable": false,
6+
"editor.formatOnSave": false,
7+
// Auto fix
8+
"editor.codeActionsOnSave": {
9+
"source.fixAll.eslint": "explicit",
10+
"source.organizeImports": "never"
11+
},
12+
// Silent the stylistic rules in you IDE, but still auto fix them
13+
"eslint.rules.customizations": [
14+
{
15+
"rule": "style/*",
16+
"severity": "off"
17+
},
18+
{
19+
"rule": "format/*",
20+
"severity": "off"
21+
},
22+
{
23+
"rule": "*-indent",
24+
"severity": "off"
25+
},
26+
{
27+
"rule": "*-spacing",
28+
"severity": "off"
29+
},
30+
{
31+
"rule": "*-spaces",
32+
"severity": "off"
33+
},
34+
{
35+
"rule": "*-order",
36+
"severity": "off"
37+
},
38+
{
39+
"rule": "*-dangle",
40+
"severity": "off"
41+
},
42+
{
43+
"rule": "*-newline",
44+
"severity": "off"
45+
},
46+
{
47+
"rule": "*quotes",
48+
"severity": "off"
49+
},
50+
{
51+
"rule": "*semi",
52+
"severity": "off"
53+
}
54+
],
55+
// Enable eslint for all supported languages
56+
"eslint.validate": [
57+
"javascript",
58+
"javascriptreact",
59+
"typescript",
60+
"typescriptreact",
61+
"vue",
62+
"html",
63+
"markdown",
64+
"json",
65+
"jsonc",
66+
"yaml",
67+
"toml"
68+
]
369
}

README.md

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ export default {
5050

5151
<br></details>
5252

53-
5453
<details>
5554
<summary>Webpack</summary><br>
5655

@@ -146,9 +145,6 @@ console.log('Verbose output version')
146145
// #endif
147146
```
148147

149-
> [!WARNING]
150-
> `#define` and `#undef` are Hoisting, like `var` in JavaScript.
151-
152148
### Conditional compilation
153149

154150
- `#if`: Opens a conditional compilation, where code is compiled only if the specified symbol is defined and evaluated to true.
@@ -204,52 +200,85 @@ You can used `defineDirective` to define your own directive.
204200
Taking the built-in directive as an example:
205201

206202
```ts
207-
/** @see https://xregexp.com/ */
208-
import type { NamedGroupsArray } from 'xregexp'
209-
import { defineDirective } from '../directive'
210-
211-
export default defineDirective<undefined>(() => ({
212-
nested: false,
213-
name: '#define',
214-
pattern: /.*?#(?<directive>(?:un)?def(?:ine)?)\s*(?<key>[\w]*)\s/gm,
215-
processor({ ctx }) {
216-
return (...args) => {
217-
const group = args[args.length - 1] as NamedGroupsArray
218-
if (group.directive === 'define')
219-
// @ts-expect-error ignore
220-
ctx.env[group.key] = true
221-
222-
else if (group.directive === 'undef')
223-
delete ctx.env[group.key]
224-
225-
return ''
203+
export const MessageDirective = defineDirective<MessageToken, MessageStatement>(context => ({
204+
lex(comment) {
205+
return simpleMatchToken(comment, /#(error|warning|info)\s*(.*)/)
206+
},
207+
parse(token) {
208+
if (token.type === 'error' || token.type === 'warning' || token.type === 'info') {
209+
this.current++
210+
return {
211+
type: 'MessageStatement',
212+
kind: token.type,
213+
value: token.value,
214+
}
215+
}
216+
},
217+
transform(node) {
218+
if (node.type === 'MessageStatement') {
219+
switch (node.kind) {
220+
case 'error':
221+
context.logger.error(node.value, { timestamp: true })
222+
break
223+
case 'warning':
224+
context.logger.warn(node.value, { timestamp: true })
225+
break
226+
case 'info':
227+
context.logger.info(node.value, { timestamp: true })
228+
break
229+
}
230+
return createProgramNode()
231+
}
232+
},
233+
generate(node, comment) {
234+
if (node.type === 'MessageStatement' && comment)
235+
return `${comment.start} #${node.kind} ${node.value} ${comment.end}`
236+
},
237+
}))
238+
export const MessageDirective = defineDirective<MessageToken, MessageStatement>(context => ({
239+
lex(comment) {
240+
return simpleMatchToken(comment, /#(error|warning|info)\s*(.*)/)
241+
},
242+
parse(token) {
243+
if (token.type === 'error' || token.type === 'warning' || token.type === 'info') {
244+
this.current++
245+
return {
246+
type: 'MessageStatement',
247+
kind: token.type,
248+
value: token.value,
249+
}
226250
}
227251
},
252+
transform(node) {
253+
if (node.type === 'MessageStatement') {
254+
switch (node.kind) {
255+
case 'error':
256+
context.logger.error(node.value, { timestamp: true })
257+
break
258+
case 'warning':
259+
context.logger.warn(node.value, { timestamp: true })
260+
break
261+
case 'info':
262+
context.logger.info(node.value, { timestamp: true })
263+
break
264+
}
265+
return createProgramNode()
266+
}
267+
},
268+
generate(node, comment) {
269+
if (node.type === 'MessageStatement' && comment)
270+
return `${comment.start} #${node.kind} ${node.value} ${comment.end}`
271+
},
228272
}))
229273
```
230274

231-
### `name: string`
232-
233-
directive name, used to identify the directive in warning and error messages
234-
235275
### `enforce: 'pre' | 'post'`
236276

237277
Execution priority of directives
238278

239279
- `pre`: Execute as early as possible
240280
- `post`: Execute as late as possible
241281

242-
### `nested: boolean`
243-
244-
Is it a nested instruction, The default is `false`. If it is `true`, `matchRecursive` will be used internally for replace and recursive calls. Otherwise, `replace` will be used`
245-
246-
### `pattern`
247-
248-
The regular expression of the directive, if it is a nested instruction, needs to specify the `start` and `end` regular expressions
249-
### `processor`
250-
251-
The processing function of the directive.
252-
253282
[npm-version-src]: https://img.shields.io/npm/v/unplugin-preprocessor-directives?style=flat&colorA=18181B&colorB=F0DB4F
254283
[npm-version-href]: https://npmjs.com/package/unplugin-preprocessor-directives
255284
[npm-downloads-src]: https://img.shields.io/npm/dm/unplugin-preprocessor-directives?style=flat&colorA=18181B&colorB=F0DB4F

README.zh-cn.md

Lines changed: 32 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ export default {
5050

5151
<br></details>
5252

53-
5453
<details>
5554
<summary>Webpack</summary><br>
5655

@@ -146,9 +145,6 @@ console.log('Verbose output version')
146145
// #endif
147146
```
148147

149-
> [!WARNING]
150-
> `#define``#undef` 是提升的,类似 JavaScript 的 `var`
151-
152148
### 条件编译
153149

154150
- `#if`: 打开条件编译,只有当指定的 symbol 被定义并求值为 true 时,代码才会被编译。
@@ -205,54 +201,50 @@ class MyClass {
205201
以内置指令为例:
206202

207203
```ts
208-
/** @see https://xregexp.com/ */
209-
import type { NamedGroupsArray } from 'xregexp'
210-
import { defineDirective } from '../directive'
211-
212-
export default defineDirective<undefined>(() => ({
213-
nested: false,
214-
name: '#define',
215-
pattern: /.*?#(?<directive>(?:un)?def(?:ine)?)\s*(?<key>[\w]*)\s/gm,
216-
processor({ ctx }) {
217-
return (...args) => {
218-
const group = args[args.length - 1] as NamedGroupsArray
219-
if (group.directive === 'define')
220-
// @ts-expect-error ignore
221-
ctx.env[group.key] = true
222-
223-
else if (group.directive === 'undef')
224-
delete ctx.env[group.key]
225-
226-
return ''
204+
export const MessageDirective = defineDirective<MessageToken, MessageStatement>(context => ({
205+
lex(comment) {
206+
return simpleMatchToken(comment, /#(error|warning|info)\s*(.*)/)
207+
},
208+
parse(token) {
209+
if (token.type === 'error' || token.type === 'warning' || token.type === 'info') {
210+
this.current++
211+
return {
212+
type: 'MessageStatement',
213+
kind: token.type,
214+
value: token.value,
215+
}
216+
}
217+
},
218+
transform(node) {
219+
if (node.type === 'MessageStatement') {
220+
switch (node.kind) {
221+
case 'error':
222+
context.logger.error(node.value, { timestamp: true })
223+
break
224+
case 'warning':
225+
context.logger.warn(node.value, { timestamp: true })
226+
break
227+
case 'info':
228+
context.logger.info(node.value, { timestamp: true })
229+
break
230+
}
231+
return createProgramNode()
227232
}
228233
},
234+
generate(node, comment) {
235+
if (node.type === 'MessageStatement' && comment)
236+
return `${comment.start} #${node.kind} ${node.value} ${comment.end}`
237+
},
229238
}))
230239
```
231240

232-
### `name: string`
233-
234-
指令的名称,用于在警告和错误消息中标识指令。
235-
236241
### `enforce: 'pre' | 'post'`
237242

238243
指令的执行优先级
239244

240245
- `pre` 尽可能早执行
241246
- `post` 尽可能晚执行
242247

243-
### `nested: boolean`
244-
245-
是否为嵌套指令,默认为 `false`,如果为 `true` 在内部将使用 `matchRecursive` 进行 `replace` 并递归调用, 否则使用 `replace`
246-
247-
### `pattern`
248-
249-
指令的正则表达式,如果是嵌套指令,需要指定开始和结束的正则表达式
250-
251-
### `processor`
252-
253-
指令的处理函数。
254-
255-
256248
[npm-version-src]: https://img.shields.io/npm/v/unplugin-preprocessor-directives?style=flat&colorA=18181B&colorB=F0DB4F
257249
[npm-version-href]: https://npmjs.com/package/unplugin-preprocessor-directives
258250
[npm-downloads-src]: https://img.shields.io/npm/dm/unplugin-preprocessor-directives?style=flat&colorA=18181B&colorB=F0DB4F

eslint.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import antfu from '@antfu/eslint-config'
2+
3+
export default antfu({
4+
ignores: ['**/test/fixtures/**/*.*'],
5+
})

examples/vite-vue-ts/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
"preview": "vite preview"
1010
},
1111
"dependencies": {
12-
"vue": "^3.3.4"
12+
"vue": "^3.4.15"
1313
},
1414
"devDependencies": {
15-
"@vitejs/plugin-vue": "^4.3.4",
16-
"typescript": "^5.2.2",
15+
"@vitejs/plugin-vue": "^5.0.3",
16+
"typescript": "^5.3.3",
1717
"unplugin-preprocessor-directives": "workspace:*",
18-
"vite": "^4.4.9",
19-
"vue-tsc": "^1.8.15"
18+
"vite": "^5.0.12",
19+
"vue-tsc": "^1.8.27"
2020
}
2121
}

examples/vite-vue-ts/tsconfig.json

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
11
{
22
"compilerOptions": {
33
"target": "ES2020",
4+
"jsx": "preserve",
5+
"lib": [
6+
"ES2020",
7+
"DOM",
8+
"DOM.Iterable"
9+
],
410
"useDefineForClassFields": true,
511
"module": "ESNext",
6-
"lib": ["ES2020", "DOM", "DOM.Iterable"],
7-
"skipLibCheck": true,
8-
912
/* Bundler mode */
1013
"moduleResolution": "bundler",
11-
"allowImportingTsExtensions": true,
1214
"resolveJsonModule": true,
13-
"isolatedModules": true,
14-
"noEmit": true,
15-
"jsx": "preserve",
16-
15+
"allowImportingTsExtensions": true,
1716
/* Linting */
1817
"strict": true,
18+
"noFallthroughCasesInSwitch": true,
1919
"noUnusedLocals": true,
2020
"noUnusedParameters": true,
21-
"noFallthroughCasesInSwitch": true
21+
"noEmit": true,
22+
"isolatedModules": true,
23+
"skipLibCheck": true
2224
},
23-
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
24-
"references": [{ "path": "./tsconfig.node.json" }]
25+
"references": [
26+
{
27+
"path": "./tsconfig.node.json"
28+
}
29+
],
30+
"include": [
31+
"src/**/*.ts",
32+
"src/**/*.d.ts",
33+
"src/**/*.tsx",
34+
"src/**/*.vue"
35+
]
2536
}

0 commit comments

Comments
 (0)