Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use AST to parse code #5

Merged
merged 19 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintignore

This file was deleted.

3 changes: 0 additions & 3 deletions .eslintrc

This file was deleted.

68 changes: 67 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,69 @@
{
"prettier.enable": false
// Enable the ESlint flat config support
"eslint.experimental.useFlatConfig": true,
// Disable the default formatter, use eslint instead
"prettier.enable": false,
"editor.formatOnSave": false,
// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
// Silent the stylistic rules in you IDE, but still auto fix them
"eslint.rules.customizations": [
{
"rule": "style/*",
"severity": "off"
},
{
"rule": "format/*",
"severity": "off"
},
{
"rule": "*-indent",
"severity": "off"
},
{
"rule": "*-spacing",
"severity": "off"
},
{
"rule": "*-spaces",
"severity": "off"
},
{
"rule": "*-order",
"severity": "off"
},
{
"rule": "*-dangle",
"severity": "off"
},
{
"rule": "*-newline",
"severity": "off"
},
{
"rule": "*quotes",
"severity": "off"
},
{
"rule": "*semi",
"severity": "off"
}
],
// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml",
"toml"
]
}
105 changes: 67 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export default {

<br></details>


<details>
<summary>Webpack</summary><br>

Expand Down Expand Up @@ -146,9 +145,6 @@ console.log('Verbose output version')
// #endif
```

> [!WARNING]
> `#define` and `#undef` are Hoisting, like `var` in JavaScript.

### Conditional compilation

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

```ts
/** @see https://xregexp.com/ */
import type { NamedGroupsArray } from 'xregexp'
import { defineDirective } from '../directive'

export default defineDirective<undefined>(() => ({
nested: false,
name: '#define',
pattern: /.*?#(?<directive>(?:un)?def(?:ine)?)\s*(?<key>[\w]*)\s/gm,
processor({ ctx }) {
return (...args) => {
const group = args[args.length - 1] as NamedGroupsArray
if (group.directive === 'define')
// @ts-expect-error ignore
ctx.env[group.key] = true

else if (group.directive === 'undef')
delete ctx.env[group.key]

return ''
export const MessageDirective = defineDirective<MessageToken, MessageStatement>(context => ({
lex(comment) {
return simpleMatchToken(comment, /#(error|warning|info)\s*(.*)/)
},
parse(token) {
if (token.type === 'error' || token.type === 'warning' || token.type === 'info') {
this.current++
return {
type: 'MessageStatement',
kind: token.type,
value: token.value,
}
}
},
transform(node) {
if (node.type === 'MessageStatement') {
switch (node.kind) {
case 'error':
context.logger.error(node.value, { timestamp: true })
break
case 'warning':
context.logger.warn(node.value, { timestamp: true })
break
case 'info':
context.logger.info(node.value, { timestamp: true })
break
}
return createProgramNode()
}
},
generate(node, comment) {
if (node.type === 'MessageStatement' && comment)
return `${comment.start} #${node.kind} ${node.value} ${comment.end}`
},
}))
export const MessageDirective = defineDirective<MessageToken, MessageStatement>(context => ({
lex(comment) {
return simpleMatchToken(comment, /#(error|warning|info)\s*(.*)/)
},
parse(token) {
if (token.type === 'error' || token.type === 'warning' || token.type === 'info') {
this.current++
return {
type: 'MessageStatement',
kind: token.type,
value: token.value,
}
}
},
transform(node) {
if (node.type === 'MessageStatement') {
switch (node.kind) {
case 'error':
context.logger.error(node.value, { timestamp: true })
break
case 'warning':
context.logger.warn(node.value, { timestamp: true })
break
case 'info':
context.logger.info(node.value, { timestamp: true })
break
}
return createProgramNode()
}
},
generate(node, comment) {
if (node.type === 'MessageStatement' && comment)
return `${comment.start} #${node.kind} ${node.value} ${comment.end}`
},
}))
```

### `name: string`

directive name, used to identify the directive in warning and error messages

### `enforce: 'pre' | 'post'`

Execution priority of directives

- `pre`: Execute as early as possible
- `post`: Execute as late as possible

### `nested: boolean`

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`

### `pattern`

The regular expression of the directive, if it is a nested instruction, needs to specify the `start` and `end` regular expressions
### `processor`

The processing function of the directive.

[npm-version-src]: https://img.shields.io/npm/v/unplugin-preprocessor-directives?style=flat&colorA=18181B&colorB=F0DB4F
[npm-version-href]: https://npmjs.com/package/unplugin-preprocessor-directives
[npm-downloads-src]: https://img.shields.io/npm/dm/unplugin-preprocessor-directives?style=flat&colorA=18181B&colorB=F0DB4F
Expand Down
72 changes: 32 additions & 40 deletions README.zh-cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export default {

<br></details>


<details>
<summary>Webpack</summary><br>

Expand Down Expand Up @@ -146,9 +145,6 @@ console.log('Verbose output version')
// #endif
```

> [!WARNING]
> `#define` 和 `#undef` 是提升的,类似 JavaScript 的 `var`。

### 条件编译

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

```ts
/** @see https://xregexp.com/ */
import type { NamedGroupsArray } from 'xregexp'
import { defineDirective } from '../directive'

export default defineDirective<undefined>(() => ({
nested: false,
name: '#define',
pattern: /.*?#(?<directive>(?:un)?def(?:ine)?)\s*(?<key>[\w]*)\s/gm,
processor({ ctx }) {
return (...args) => {
const group = args[args.length - 1] as NamedGroupsArray
if (group.directive === 'define')
// @ts-expect-error ignore
ctx.env[group.key] = true

else if (group.directive === 'undef')
delete ctx.env[group.key]

return ''
export const MessageDirective = defineDirective<MessageToken, MessageStatement>(context => ({
lex(comment) {
return simpleMatchToken(comment, /#(error|warning|info)\s*(.*)/)
},
parse(token) {
if (token.type === 'error' || token.type === 'warning' || token.type === 'info') {
this.current++
return {
type: 'MessageStatement',
kind: token.type,
value: token.value,
}
}
},
transform(node) {
if (node.type === 'MessageStatement') {
switch (node.kind) {
case 'error':
context.logger.error(node.value, { timestamp: true })
break
case 'warning':
context.logger.warn(node.value, { timestamp: true })
break
case 'info':
context.logger.info(node.value, { timestamp: true })
break
}
return createProgramNode()
}
},
generate(node, comment) {
if (node.type === 'MessageStatement' && comment)
return `${comment.start} #${node.kind} ${node.value} ${comment.end}`
},
}))
```

### `name: string`

指令的名称,用于在警告和错误消息中标识指令。

### `enforce: 'pre' | 'post'`

指令的执行优先级

- `pre` 尽可能早执行
- `post` 尽可能晚执行

### `nested: boolean`

是否为嵌套指令,默认为 `false`,如果为 `true` 在内部将使用 `matchRecursive` 进行 `replace` 并递归调用, 否则使用 `replace`

### `pattern`

指令的正则表达式,如果是嵌套指令,需要指定开始和结束的正则表达式

### `processor`

指令的处理函数。


[npm-version-src]: https://img.shields.io/npm/v/unplugin-preprocessor-directives?style=flat&colorA=18181B&colorB=F0DB4F
[npm-version-href]: https://npmjs.com/package/unplugin-preprocessor-directives
[npm-downloads-src]: https://img.shields.io/npm/dm/unplugin-preprocessor-directives?style=flat&colorA=18181B&colorB=F0DB4F
Expand Down
5 changes: 5 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import antfu from '@antfu/eslint-config'

export default antfu({
ignores: ['**/test/fixtures/**/*.*'],
})
10 changes: 5 additions & 5 deletions examples/vite-vue-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.3.4"
"vue": "^3.4.15"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.3.4",
"typescript": "^5.2.2",
"@vitejs/plugin-vue": "^5.0.3",
"typescript": "^5.3.3",
"unplugin-preprocessor-directives": "workspace:*",
"vite": "^4.4.9",
"vue-tsc": "^1.8.15"
"vite": "^5.0.12",
"vue-tsc": "^1.8.27"
}
}
33 changes: 22 additions & 11 deletions examples/vite-vue-ts/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
{
"compilerOptions": {
"target": "ES2020",
"jsx": "preserve",
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",

"allowImportingTsExtensions": true,
/* Linting */
"strict": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noEmit": true,
"isolatedModules": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
"references": [
{
"path": "./tsconfig.node.json"
}
],
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
]
}
Loading