refactor: lint

This commit is contained in:
Kevin Deng
2025-11-26 17:12:35 +08:00
parent 620450b5ba
commit 5279b2eb69
33 changed files with 2271 additions and 449 deletions

View File

@@ -17,7 +17,7 @@ npm install @vue/babel-plugin-jsx -D
配置 Babel
```js
```json
{
"plugins": ["@vue/babel-plugin-jsx"]
}
@@ -102,7 +102,7 @@ const App = {
```
```jsx
import { withModifiers, defineComponent } from 'vue'
import { defineComponent, withModifiers } from 'vue'
const App = defineComponent({
setup() {
@@ -169,13 +169,13 @@ const App = {
```
```jsx
<input v-model={[val, ['modifier']]} />
<input v-model={[val, ['modifier']]} />;
// 或者
<input v-model_modifier={val} />
```
```jsx
<A v-model={[val, 'argument', ['modifier']]} />
<A v-model={[val, 'argument', ['modifier']]} />;
// 或者
<input v-model:argument_modifier={val} />
```
@@ -227,7 +227,7 @@ h(A, {
modifier: true,
},
'onUpdate:modelValue': ($event) => (foo = $event),
bar: bar,
bar,
barModifiers: {
modifier: true,
},
@@ -284,7 +284,7 @@ const App = {
// or
const App = {
const App2 = {
setup() {
const slots = {
default: () => <div>A</div>,
@@ -295,7 +295,7 @@ const App = {
}
// 或者,当 `enableObjectSlots` 不是 `false` 时,您可以使用对象插槽
const App = {
const App3 = {
setup() {
return () => (
<>

View File

@@ -106,7 +106,7 @@ const App = {
```
```jsx
import { withModifiers, defineComponent } from 'vue'
import { defineComponent, withModifiers } from 'vue'
const App = defineComponent({
setup() {
@@ -173,13 +173,13 @@ const App = {
```
```jsx
<input v-model={[val, ['modifier']]} />
<input v-model={[val, ['modifier']]} />;
// Or
<input v-model_modifier={val} />
```
```jsx
<A v-model={[val, 'argument', ['modifier']]} />
<A v-model={[val, 'argument', ['modifier']]} />;
// Or
<input v-model:argument_modifier={val} />
```
@@ -231,7 +231,7 @@ h(A, {
modifier: true,
},
'onUpdate:modelValue': ($event) => (foo = $event),
bar: bar,
bar,
barModifiers: {
modifier: true,
},
@@ -288,7 +288,7 @@ const App = {
// or
const App = {
const App2 = {
setup() {
const slots = {
default: () => <div>A</div>,
@@ -299,7 +299,7 @@ const App = {
}
// or you can use object slots when `enableObjectSlots` is not false.
const App = {
const App3 = {
setup() {
return () => (
<>

View File

@@ -2,37 +2,45 @@
"name": "@vue/babel-plugin-jsx",
"version": "2.0.1",
"description": "Babel plugin for Vue 3 JSX",
"author": "Amour1688 <lcz_1996@foxmail.com>",
"homepage": "https://github.com/vuejs/babel-plugin-jsx/tree/dev/packages/babel-plugin-jsx#readme",
"license": "MIT",
"type": "module",
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"exports": {
".": {
"dev": "./src/index.ts",
"default": "./dist/index.mjs"
},
"./package.json": "./package.json"
},
"publishConfig": {
"exports": {
".": "./dist/index.mjs",
"./package.json": "./package.json"
}
"license": "MIT",
"homepage": "https://github.com/vuejs/babel-plugin-jsx/tree/dev/packages/babel-plugin-jsx#readme",
"bugs": {
"url": "https://github.com/vuejs/babel-plugin-jsx/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/babel-plugin-jsx.git",
"directory": "packages/babel-plugin-jsx"
},
"bugs": {
"url": "https://github.com/vuejs/babel-plugin-jsx/issues"
},
"author": "Amour1688 <lcz_1996@foxmail.com>",
"files": [
"dist"
],
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"dev": "./src/index.ts",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"publishConfig": {
"exports": {
".": "./dist/index.js",
"./package.json": "./package.json"
}
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
}
},
"dependencies": {
"@babel/helper-module-imports": "^7.27.1",
"@babel/helper-plugin-utils": "^7.27.1",
@@ -52,13 +60,5 @@
"@vue/test-utils": "^2.4.6",
"regenerator-runtime": "^0.14.1",
"vue": "catalog:"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
}
}
}

View File

@@ -1,15 +1,15 @@
import t from '@babel/types'
import type * as BabelCore from '@babel/core'
import _template from '@babel/template'
import { addNamed, addNamespace, isModule } from '@babel/helper-module-imports'
import { declare } from '@babel/helper-plugin-utils'
// @ts-expect-error
import _syntaxJsx from '@babel/plugin-syntax-jsx'
import { addNamed, addNamespace, isModule } from '@babel/helper-module-imports'
import { type NodePath, type Visitor } from '@babel/traverse'
import _template from '@babel/template'
import t from '@babel/types'
import ResolveType from '@vue/babel-plugin-resolve-type'
import { declare } from '@babel/helper-plugin-utils'
import transformVueJSX from './transform-vue-jsx'
import sugarFragment from './sugar-fragment'
import transformVueJSX from './transform-vue-jsx'
import type { State, VueJSXPluginOptions } from './interface'
import type * as BabelCore from '@babel/core'
import type { NodePath, Visitor } from '@babel/traverse'
export { VueJSXPluginOptions }
@@ -30,7 +30,7 @@ const hasJSX = (parentPath: NodePath<t.Program>) => {
return fileHasJSX
}
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+(\S+)/
/* #__NO_SIDE_EFFECTS__ */
function interopDefault(m: any) {
@@ -55,7 +55,7 @@ const plugin: (
resolveType = ResolveType(api, opt.resolveType, dirname)
}
return {
...(resolveType || {}),
...resolveType,
name: 'babel-plugin-jsx',
inherits: /*#__PURE__*/ interopDefault(syntaxJsx),
visitor: {
@@ -115,9 +115,9 @@ const plugin: (
return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${isVNodeName}(s));
}
`
const lastImport = (path.get('body') as NodePath[])
.filter((p) => p.isImportDeclaration())
.pop()
const lastImport = (path.get('body') as NodePath[]).findLast(
(p) => p.isImportDeclaration(),
)
if (lastImport) {
lastImport.insertAfter(ast)
}
@@ -160,16 +160,13 @@ const plugin: (
`
const nodePaths = path.get('body') as NodePath[]
const lastImport = nodePaths
.filter(
(p) =>
p.isVariableDeclaration() &&
p.node.declarations.some(
(d) =>
(d.id as t.Identifier)?.name === sourceName.name,
),
)
.pop()
const lastImport = nodePaths.findLast(
(p) =>
p.isVariableDeclaration() &&
p.node.declarations.some(
(d) => (d.id as t.Identifier)?.name === sourceName.name,
),
)
if (lastImport) {
lastImport.insertAfter(ast)
}

View File

@@ -1,6 +1,6 @@
import type t from '@babel/types'
import type * as BabelCore from '@babel/core'
import { type Options } from '@vue/babel-plugin-resolve-type'
import type t from '@babel/types'
import type { Options } from '@vue/babel-plugin-resolve-type'
export type Slots = t.Identifier | t.ObjectExpression | null

View File

@@ -1,7 +1,7 @@
import t from '@babel/types'
import { type NodePath } from '@babel/traverse'
import { createIdentifier } from './utils'
import type { State } from './interface'
import type { NodePath } from '@babel/traverse'
export type Tag =
| t.Identifier
@@ -187,8 +187,7 @@ const resolveDirective = (
}
return modelToUse
}
const referenceName =
'v' + directiveName[0].toUpperCase() + directiveName.slice(1)
const referenceName = `v${directiveName[0].toUpperCase()}${directiveName.slice(1)}`
if (path.scope.references[referenceName]) {
return t.identifier(referenceName)
}

View File

@@ -1,6 +1,6 @@
// https://github.com/vuejs/core/blob/main/packages/shared/src/patchFlags.ts
export const enum PatchFlags {
export enum PatchFlags {
TEXT = 1,
CLASS = 1 << 1,
STYLE = 1 << 2,

View File

@@ -1,5 +1,5 @@
// https://github.com/vuejs/core/blob/main/packages/shared/src/slotFlags.ts
const enum SlotFlags {
export enum SlotFlags {
/**
* Stable slots that only reference slot props or context state. The slot
* can fully capture its own dependencies so when passed down the parent won't
@@ -20,5 +20,3 @@ const enum SlotFlags {
*/
FORWARDED = 3,
}
export default SlotFlags

View File

@@ -1,7 +1,7 @@
import t from '@babel/types'
import { type NodePath, type Visitor } from '@babel/traverse'
import { createIdentifier, FRAGMENT } from './utils'
import type { State } from './interface'
import { FRAGMENT, createIdentifier } from './utils'
import type { NodePath, Visitor } from '@babel/traverse'
const transformFragment = (
path: NodePath<t.JSXFragment>,

View File

@@ -1,6 +1,8 @@
import t from '@babel/types'
import { type NodePath, type Visitor } from '@babel/traverse'
import { addDefault } from '@babel/helper-module-imports'
import t from '@babel/types'
import parseDirectives from './parseDirectives'
import { PatchFlags } from './patchFlags'
import { SlotFlags } from './slotFlags'
import {
buildIIFE,
checkIsComponent,
@@ -18,10 +20,8 @@ import {
transformText,
walksScope,
} from './utils'
import SlotFlags from './slotFlags'
import { PatchFlags } from './patchFlags'
import parseDirectives from './parseDirectives'
import type { Slots, State } from './interface'
import type { NodePath, Visitor } from '@babel/traverse'
const xlinkRE = /^xlink([A-Z])/
@@ -229,15 +229,15 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
),
)
if (!isDynamic) {
dynamicPropNames.add((updateName as t.StringLiteral).value)
} else {
if (isDynamic) {
hasDynamicKeys = true
} else {
dynamicPropNames.add((updateName as t.StringLiteral).value)
}
})
}
} else {
if (name.match(xlinkRE)) {
if (xlinkRE.test(name)) {
name = name.replace(
xlinkRE,
(_, firstCharacter) => `xlink:${firstCharacter.toLowerCase()}`,

View File

@@ -1,19 +1,12 @@
import t from '@babel/types'
import { type NodePath } from '@babel/traverse'
import { isHTMLTag, isSVGTag } from '@vue/shared'
import { SlotFlags } from './slotFlags'
import type { State } from './interface'
import SlotFlags from './slotFlags'
import type { NodePath } from '@babel/traverse'
export const JSX_HELPER_KEY = 'JSX_HELPER_KEY'
export const FRAGMENT = 'Fragment'
export const KEEP_ALIVE = 'KeepAlive'
/**
* create Identifier
* @param path NodePath
* @param state
* @param name string
* @returns MemberExpression
*/
export const createIdentifier = (
state: State,
name: string,
@@ -34,14 +27,10 @@ export const isDirective = (src: string): boolean =>
*/
// if _Fragment is already imported, it will end with number
export const shouldTransformedToSlots = (tag: string) =>
!(tag.match(RegExp(`^_?${FRAGMENT}\\d*$`)) || tag === KEEP_ALIVE)
!new RegExp(String.raw`^_?${FRAGMENT}\d*$`).test(tag) && tag !== KEEP_ALIVE
/**
* Check if a Node is a component
*
* @param t
* @param path JSXOpeningElement
* @returns boolean
*/
export const checkIsComponent = (
path: NodePath<t.JSXOpeningElement>,
@@ -136,7 +125,7 @@ export const transformJSXText = (
path: NodePath<t.JSXText | t.StringLiteral>,
): t.StringLiteral | null => {
const str = transformText(path.node.value)
return str !== '' ? t.stringLiteral(str) : null
return str === '' ? null : t.stringLiteral(str)
}
export const transformText = (text: string) => {
@@ -144,8 +133,8 @@ export const transformText = (text: string) => {
let lastNonEmptyLine = 0
for (let i = 0; i < lines.length; i++) {
if (lines[i].match(/[^ \t]/)) {
for (const [i, line] of lines.entries()) {
if (/[^ \t]/.test(line)) {
lastNonEmptyLine = i
}
}
@@ -160,16 +149,16 @@ export const transformText = (text: string) => {
const isLastNonEmptyLine = i === lastNonEmptyLine
// replace rendered whitespace tabs with spaces
let trimmedLine = line.replace(/\t/g, ' ')
let trimmedLine = line.replaceAll('\t', ' ')
// trim whitespace touching a newline
if (!isFirstLine) {
trimmedLine = trimmedLine.replace(/^[ ]+/, '')
trimmedLine = trimmedLine.replace(/^ +/, '')
}
// trim whitespace touching an endline
if (!isLastLine) {
trimmedLine = trimmedLine.replace(/[ ]+$/, '')
trimmedLine = trimmedLine.replace(/ +$/, '')
}
if (trimmedLine) {

View File

@@ -1,12 +1,12 @@
import { mount, shallowMount, type VueWrapper } from '@vue/test-utils'
import {
type CSSProperties,
type ComponentPublicInstance,
Transition,
defineComponent,
reactive,
ref,
Transition,
type ComponentPublicInstance,
type CSSProperties,
} from 'vue'
import { type VueWrapper, mount, shallowMount } from '@vue/test-utils'
const patchFlagExpect = (
wrapper: VueWrapper<ComponentPublicInstance>,
@@ -129,21 +129,21 @@ describe('Transform JSX', () => {
return () => <div class="a" {...{ class: 'b' }} />
},
})
expect(wrapper.classes().sort()).toEqual(['a', 'b'].sort())
expect(wrapper.classes().toSorted()).toEqual(['a', 'b'].toSorted())
})
test('Merge style', () => {
const propsA = {
style: {
color: 'red',
} as CSSProperties,
} satisfies CSSProperties,
}
const propsB = {
style: {
color: 'blue',
width: '300px',
height: '300px',
} as CSSProperties,
} satisfies CSSProperties,
}
const wrapper = shallowMount({
setup() {
@@ -369,7 +369,10 @@ describe('PatchFlags', () => {
setup() {
const foo = ref(0)
return () => (
<button value={`${foo.value}`} onClick={() => foo.value++}></button>
<button
value={String(foo.value)}
onClick={() => foo.value++}
></button>
)
},
})
@@ -397,7 +400,7 @@ describe('PatchFlags', () => {
await wrapper.trigger('click')
expect(wrapper.classes().sort()).toEqual(['b', 'static'].sort())
expect(wrapper.classes().toSorted()).toEqual(['b', 'static'].toSorted())
})
})
@@ -428,7 +431,7 @@ describe('variables outside slots', () => {
},
render() {
const attrs = {
innerHTML: `${this.val}`,
innerHTML: String(this.val),
}
return (
<A inc={this.inc}>
@@ -466,7 +469,7 @@ describe('variables outside slots', () => {
},
render() {
const attrs = {
innerHTML: `${this.val}`,
innerHTML: String(this.val),
}
const textarea = <textarea id="textarea" {...attrs} />
return (

View File

@@ -1,5 +1,5 @@
import { mount, shallowMount } from '@vue/test-utils'
import { type VNode, defineComponent } from 'vue'
import { defineComponent, type VNode } from 'vue'
test('input[type="checkbox"] should work', async () => {
const wrapper = shallowMount(