From 2cb2ab0d370f7a0c1101da87d03895a781438552 Mon Sep 17 00:00:00 2001 From: waynzh Date: Sun, 10 Dec 2023 20:46:41 +0800 Subject: [PATCH 1/6] feat(define-macros-order): add `defineExposeLast` option --- lib/rules/define-macros-order.js | 74 ++++++++++++++++++- lib/utils/index.js | 9 +++ tests/lib/rules/define-macros-order.js | 66 +++++++++++++++++ typings/eslint-plugin-vue/util-types/utils.ts | 2 + 4 files changed, 150 insertions(+), 1 deletion(-) diff --git a/lib/rules/define-macros-order.js b/lib/rules/define-macros-order.js index 5a678db42..476e37b43 100644 --- a/lib/rules/define-macros-order.js +++ b/lib/rules/define-macros-order.js @@ -95,8 +95,12 @@ function create(context) { const options = context.options /** @type {[string, string]} */ const order = (options[0] && options[0].order) || DEFAULT_ORDER + /** @type {boolean} */ + const defineExposeLast = (options[0] && options[0].defineExposeLast) || false /** @type {Map} */ const macrosNodes = new Map() + /** @type {ASTNode} */ + let defineExposeNode return utils.compositingVisitors( utils.defineScriptSetupVisitor(context, { @@ -111,6 +115,9 @@ function create(context) { }, onDefineSlotsExit(node) { macrosNodes.set(MACROS_SLOTS, getDefineMacrosStatement(node)) + }, + onDefineExposeExit(node) { + defineExposeNode = getDefineMacrosStatement(node) } }), { @@ -131,6 +138,14 @@ function create(context) { (data) => utils.isDef(data.node) ) + // check last node + if (defineExposeLast) { + const lastNode = program.body[program.body.length - 1] + if (defineExposeNode && lastNode !== defineExposeNode) { + reportExposeNotOnBottom(defineExposeNode, lastNode) + } + } + for (const [index, should] of orderedList.entries()) { const targetStatement = program.body[firstStatementIndex + index] @@ -172,6 +187,58 @@ function create(context) { }) } + /** + * @param {ASTNode} node + * @param {ASTNode} lastNode + */ + function reportExposeNotOnBottom(node, lastNode) { + context.report({ + node, + loc: node.loc, + messageId: 'defineExposeNotTheLast', + fix(fixer) { + return moveNodeToLast(fixer, node, lastNode) + } + }) + } + + /** + * Move all lines of "node" with its comments to after the "target" + * @param {RuleFixer} fixer + * @param {ASTNode} node + * @param {ASTNode} target + */ + function moveNodeToLast(fixer, node, target) { + // get comments under tokens(if any) + const beforeNodeToken = sourceCode.getTokenBefore(node) + const nodeComment = sourceCode.getTokenAfter(beforeNodeToken, { + includeComments: true + }) + const nextNodeComment = sourceCode.getTokenAfter(node, { + includeComments: true + }) + + // remove position: node (and comments) to next node (and comments) + const cutStart = getLineStartIndex(nodeComment, beforeNodeToken) + const cutEnd = getLineStartIndex(nextNodeComment, node) + + // insert text: comment + node + const textNode = sourceCode.getText( + node, + node.range[0] - beforeNodeToken.range[1] + ) + + // insert position: after target and comments (if any) + const afterTargetComment = sourceCode.getTokenAfter(target, { + includeComments: true + }) + + return [ + fixer.insertTextAfter(afterTargetComment, textNode), + fixer.removeRange([cutStart, cutEnd]) + ] + } + /** * Move all lines of "node" with its comments to before the "target" * @param {RuleFixer} fixer @@ -266,6 +333,9 @@ module.exports = { }, uniqueItems: true, additionalItems: false + }, + defineExposeLast: { + type: 'boolean' } }, additionalProperties: false @@ -273,7 +343,9 @@ module.exports = { ], messages: { macrosNotOnTop: - '{{macro}} should be the first statement in ` + `, + options: optionsExposeLast } ], invalid: [ @@ -622,6 +646,48 @@ tester.run('define-macros-order', rule, { line: 6 } ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: optionsExposeLast, + errors: [ + { + message: defineExposeNotTheLast, + line: 8 + } + ] } ] }) diff --git a/typings/eslint-plugin-vue/util-types/utils.ts b/typings/eslint-plugin-vue/util-types/utils.ts index b2704769f..4d8384d66 100644 --- a/typings/eslint-plugin-vue/util-types/utils.ts +++ b/typings/eslint-plugin-vue/util-types/utils.ts @@ -44,6 +44,8 @@ export interface ScriptSetupVisitor extends ScriptSetupVisitorBase { onDefineOptionsExit?(node: CallExpression): void onDefineSlotsEnter?(node: CallExpression): void onDefineSlotsExit?(node: CallExpression): void + onDefineExposeEnter?(node: CallExpression): void + onDefineExposeExit?(node: CallExpression): void [query: string]: | ((node: VAST.ParamNode) => void) | ((node: CallExpression, props: ComponentProp[]) => void) From 669e864ded58d9d9537d9d09badf42e30b50bd68 Mon Sep 17 00:00:00 2001 From: waynzh Date: Thu, 21 Dec 2023 22:58:06 +0800 Subject: [PATCH 2/6] feat: update test cases --- tests/lib/rules/define-macros-order.js | 102 +++++++++++++++++++++---- 1 file changed, 88 insertions(+), 14 deletions(-) diff --git a/tests/lib/rules/define-macros-order.js b/tests/lib/rules/define-macros-order.js index 525275b3b..fc115eaa5 100644 --- a/tests/lib/rules/define-macros-order.js +++ b/tests/lib/rules/define-macros-order.js @@ -184,16 +184,43 @@ tester.run('define-macros-order', rule, { filename: 'test.vue', code: ` + `, + options: optionsExposeLast + }, + { + filename: 'test.vue', + code: ` + `, - options: optionsExposeLast + options: [ + { + order: ['defineProps', 'defineEmits'], + defineExposeLast: true + } + ], + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } } ], invalid: [ @@ -655,14 +682,14 @@ tester.run('define-macros-order', rule, { test: Boolean }) - // expose + /** expose */ defineExpose({ - a: 1 + foo: 'bar' }) - // console start + /** console start */ console.log('test') - // console end + /** console end */ `, output: ` @@ -671,13 +698,13 @@ tester.run('define-macros-order', rule, { test: Boolean }) - // console start + /** console start */ console.log('test') - // console end + /** console end */ - // expose + /** expose */ defineExpose({ - a: 1 + foo: 'bar' }) `, @@ -688,6 +715,53 @@ tester.run('define-macros-order', rule, { line: 8 } ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [ + { + order: ['defineOptions', 'defineEmits', 'defineProps', 'defineSlots'], + defineExposeLast: true + } + ], + errors: [ + { + message: message('defineOptions'), + line: 6 + }, + { + message: defineExposeNotTheLast, + line: 10 + } + ] } ] }) From 757a7e647bda63e26873857ded72aa097f06b7ae Mon Sep 17 00:00:00 2001 From: waynzh Date: Thu, 21 Dec 2023 23:17:31 +0800 Subject: [PATCH 3/6] docs: add defineExposeLast option --- docs/rules/define-macros-order.md | 33 ++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/rules/define-macros-order.md b/docs/rules/define-macros-order.md index 37074f05c..de3916147 100644 --- a/docs/rules/define-macros-order.md +++ b/docs/rules/define-macros-order.md @@ -20,12 +20,14 @@ This rule reports the `defineProps` and `defineEmits` compiler macros when they ```json { "vue/define-macros-order": ["error", { - "order": ["defineProps", "defineEmits"] + "order": ["defineProps", "defineEmits"], + "defineExposeLast": false, }] } ``` - `order` (`string[]`) ... The order of defineEmits and defineProps macros. You can also add `"defineOptions"` and `"defineSlots"`. +- `defineExposeLast` (`boolean`) ... Force `defineExpose` at the end. ### `{ "order": ["defineProps", "defineEmits"] }` (default) @@ -111,6 +113,35 @@ const slots = defineSlots() +### `{ "defineExposeLast": true }` + + +```vue + + +``` + + + + + +```vue + + +``` + + + ## :rocket: Version This rule was introduced in eslint-plugin-vue v8.7.0 From dc6f977eb1ec2089484fae696c8d56ee40e17c08 Mon Sep 17 00:00:00 2001 From: waynzh Date: Thu, 21 Dec 2023 23:21:55 +0800 Subject: [PATCH 4/6] docs: format --- docs/rules/define-macros-order.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/define-macros-order.md b/docs/rules/define-macros-order.md index de3916147..11585afac 100644 --- a/docs/rules/define-macros-order.md +++ b/docs/rules/define-macros-order.md @@ -21,7 +21,7 @@ This rule reports the `defineProps` and `defineEmits` compiler macros when they { "vue/define-macros-order": ["error", { "order": ["defineProps", "defineEmits"], - "defineExposeLast": false, + "defineExposeLast": false }] } ``` From c16c5e469cc6666e0b2ab9d86f09c058eb6f670f Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Thu, 21 Dec 2023 17:16:41 +0100 Subject: [PATCH 5/6] Fix lint issue --- docs/rules/define-macros-order.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/rules/define-macros-order.md b/docs/rules/define-macros-order.md index 11585afac..24d25c56e 100644 --- a/docs/rules/define-macros-order.md +++ b/docs/rules/define-macros-order.md @@ -114,6 +114,7 @@ const slots = defineSlots() ### `{ "defineExposeLast": true }` + ```vue From 3c704bb7b9215fb6fecc48088a5de1a2ddcb26f3 Mon Sep 17 00:00:00 2001 From: waynzh Date: Mon, 8 Jan 2024 00:12:49 +0800 Subject: [PATCH 6/6] feat: change to suggestion --- lib/rules/define-macros-order.js | 23 +++--- tests/lib/rules/define-macros-order.js | 96 +++++++++++++++----------- 2 files changed, 69 insertions(+), 50 deletions(-) diff --git a/lib/rules/define-macros-order.js b/lib/rules/define-macros-order.js index 476e37b43..b55c5e8c7 100644 --- a/lib/rules/define-macros-order.js +++ b/lib/rules/define-macros-order.js @@ -196,9 +196,14 @@ function create(context) { node, loc: node.loc, messageId: 'defineExposeNotTheLast', - fix(fixer) { - return moveNodeToLast(fixer, node, lastNode) - } + suggest: [ + { + messageId: 'putExposeAtTheLast', + fix(fixer) { + return moveNodeToLast(fixer, node, lastNode) + } + } + ] }) } @@ -228,13 +233,8 @@ function create(context) { node.range[0] - beforeNodeToken.range[1] ) - // insert position: after target and comments (if any) - const afterTargetComment = sourceCode.getTokenAfter(target, { - includeComments: true - }) - return [ - fixer.insertTextAfter(afterTargetComment, textNode), + fixer.insertTextAfter(target, textNode), fixer.removeRange([cutStart, cutEnd]) ] } @@ -322,6 +322,7 @@ module.exports = { url: 'https://eslint.vuejs.org/rules/define-macros-order.html' }, fixable: 'code', + hasSuggestions: true, schema: [ { type: 'object', @@ -345,7 +346,9 @@ module.exports = { macrosNotOnTop: '{{macro}} should be the first statement in ` - `, - output: ` - `, + output: null, options: optionsExposeLast, errors: [ { message: defineExposeNotTheLast, - line: 8 + line: 6, + suggestions: [ + { + desc: putExposeAtBottom, + output: ` + + ` + } + ] } ] }, @@ -720,16 +717,16 @@ tester.run('define-macros-order', rule, { filename: 'test.vue', code: ` `, output: ` @@ -738,28 +735,47 @@ tester.run('define-macros-order', rule, { defineOptions({}) /** emits */ defineEmits(['update:foo']) + /** expose */ + defineExpose({}) /** props */ const props = defineProps(['foo']) /** slots */ const slots = defineSlots() - /** expose */ - defineExpose({}) `, options: [ { - order: ['defineOptions', 'defineEmits', 'defineProps', 'defineSlots'], + order: ['defineOptions', 'defineEmits', 'defineProps'], defineExposeLast: true } ], errors: [ { - message: message('defineOptions'), - line: 6 + message: defineExposeNotTheLast, + line: 6, + suggestions: [ + { + desc: putExposeAtBottom, + output: ` + + ` + } + ] }, { - message: defineExposeNotTheLast, - line: 10 + message: message('defineOptions'), + line: 8 } ] }