Tracking de l'application VApp (IHM du jeu)
This commit is contained in:
371
VApp/node_modules/eslint-plugin-vue/lib/rules/define-macros-order.js
generated
vendored
Normal file
371
VApp/node_modules/eslint-plugin-vue/lib/rules/define-macros-order.js
generated
vendored
Normal file
@ -0,0 +1,371 @@
|
||||
/**
|
||||
* @author Eduard Deisling
|
||||
* See LICENSE file in root directory for full license.
|
||||
*/
|
||||
'use strict'
|
||||
|
||||
const utils = require('../utils')
|
||||
|
||||
const MACROS_EMITS = 'defineEmits'
|
||||
const MACROS_PROPS = 'defineProps'
|
||||
const MACROS_OPTIONS = 'defineOptions'
|
||||
const MACROS_SLOTS = 'defineSlots'
|
||||
const MACROS_MODEL = 'defineModel'
|
||||
const ORDER_SCHEMA = [
|
||||
MACROS_EMITS,
|
||||
MACROS_PROPS,
|
||||
MACROS_OPTIONS,
|
||||
MACROS_SLOTS,
|
||||
MACROS_MODEL
|
||||
]
|
||||
const DEFAULT_ORDER = [MACROS_PROPS, MACROS_EMITS]
|
||||
|
||||
/**
|
||||
* @param {VElement} scriptSetup
|
||||
* @param {ASTNode} node
|
||||
*/
|
||||
function inScriptSetup(scriptSetup, node) {
|
||||
return (
|
||||
scriptSetup.range[0] <= node.range[0] &&
|
||||
node.range[1] <= scriptSetup.range[1]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ASTNode} node
|
||||
*/
|
||||
function isUseStrictStatement(node) {
|
||||
return (
|
||||
node.type === 'ExpressionStatement' &&
|
||||
node.expression.type === 'Literal' &&
|
||||
node.expression.value === 'use strict'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an index of the first statement after imports and interfaces in order
|
||||
* to place defineEmits and defineProps before this statement
|
||||
* @param {VElement} scriptSetup
|
||||
* @param {Program} program
|
||||
*/
|
||||
function getTargetStatementPosition(scriptSetup, program) {
|
||||
const skipStatements = new Set([
|
||||
'ImportDeclaration',
|
||||
'TSInterfaceDeclaration',
|
||||
'TSTypeAliasDeclaration',
|
||||
'DebuggerStatement',
|
||||
'EmptyStatement',
|
||||
'ExportNamedDeclaration'
|
||||
])
|
||||
|
||||
for (const [index, item] of program.body.entries()) {
|
||||
if (
|
||||
inScriptSetup(scriptSetup, item) &&
|
||||
!skipStatements.has(item.type) &&
|
||||
!isUseStrictStatement(item)
|
||||
) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to handle cases like "const props = defineProps(...)"
|
||||
* Define macros must be used only on top, so we can look for "Program" type
|
||||
* inside node.parent.type
|
||||
* @param {CallExpression|ASTNode} node
|
||||
* @return {ASTNode}
|
||||
*/
|
||||
function getDefineMacrosStatement(node) {
|
||||
if (!node.parent) {
|
||||
throw new Error('Node has no parent')
|
||||
}
|
||||
|
||||
if (node.parent.type === 'Program') {
|
||||
return node
|
||||
}
|
||||
|
||||
return getDefineMacrosStatement(node.parent)
|
||||
}
|
||||
|
||||
/** @param {RuleContext} context */
|
||||
function create(context) {
|
||||
const scriptSetup = utils.getScriptSetupElement(context)
|
||||
|
||||
if (!scriptSetup) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const sourceCode = context.getSourceCode()
|
||||
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<string, ASTNode[]>} */
|
||||
const macrosNodes = new Map()
|
||||
/** @type {ASTNode} */
|
||||
let defineExposeNode
|
||||
|
||||
return utils.compositingVisitors(
|
||||
utils.defineScriptSetupVisitor(context, {
|
||||
onDefinePropsExit(node) {
|
||||
macrosNodes.set(MACROS_PROPS, [getDefineMacrosStatement(node)])
|
||||
},
|
||||
onDefineEmitsExit(node) {
|
||||
macrosNodes.set(MACROS_EMITS, [getDefineMacrosStatement(node)])
|
||||
},
|
||||
onDefineOptionsExit(node) {
|
||||
macrosNodes.set(MACROS_OPTIONS, [getDefineMacrosStatement(node)])
|
||||
},
|
||||
onDefineSlotsExit(node) {
|
||||
macrosNodes.set(MACROS_SLOTS, [getDefineMacrosStatement(node)])
|
||||
},
|
||||
onDefineModelExit(node) {
|
||||
const currentModelMacros = macrosNodes.get(MACROS_MODEL) ?? []
|
||||
currentModelMacros.push(getDefineMacrosStatement(node))
|
||||
macrosNodes.set(MACROS_MODEL, currentModelMacros)
|
||||
},
|
||||
onDefineExposeExit(node) {
|
||||
defineExposeNode = getDefineMacrosStatement(node)
|
||||
}
|
||||
}),
|
||||
{
|
||||
'Program:exit'(program) {
|
||||
/**
|
||||
* @typedef {object} OrderedData
|
||||
* @property {string} name
|
||||
* @property {ASTNode} node
|
||||
*/
|
||||
const firstStatementIndex = getTargetStatementPosition(
|
||||
scriptSetup,
|
||||
program
|
||||
)
|
||||
const orderedList = order
|
||||
.flatMap((name) => {
|
||||
const nodes = macrosNodes.get(name) ?? []
|
||||
return nodes.map((node) => ({ name, node }))
|
||||
})
|
||||
.filter(
|
||||
/** @returns {data is OrderedData} */
|
||||
(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]
|
||||
|
||||
if (should.node !== targetStatement) {
|
||||
let moveTargetNodes = orderedList
|
||||
.slice(index)
|
||||
.map(({ node }) => node)
|
||||
const targetStatementIndex =
|
||||
moveTargetNodes.indexOf(targetStatement)
|
||||
|
||||
if (targetStatementIndex >= 0) {
|
||||
moveTargetNodes = moveTargetNodes.slice(0, targetStatementIndex)
|
||||
}
|
||||
reportNotOnTop(should.name, moveTargetNodes, targetStatement)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @param {string} macro
|
||||
* @param {ASTNode[]} nodes
|
||||
* @param {ASTNode} before
|
||||
*/
|
||||
function reportNotOnTop(macro, nodes, before) {
|
||||
context.report({
|
||||
node: nodes[0],
|
||||
loc: nodes[0].loc,
|
||||
messageId: 'macrosNotOnTop',
|
||||
data: {
|
||||
macro
|
||||
},
|
||||
*fix(fixer) {
|
||||
for (const node of nodes) {
|
||||
yield* moveNodeBefore(fixer, node, before)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ASTNode} node
|
||||
* @param {ASTNode} lastNode
|
||||
*/
|
||||
function reportExposeNotOnBottom(node, lastNode) {
|
||||
context.report({
|
||||
node,
|
||||
loc: node.loc,
|
||||
messageId: 'defineExposeNotTheLast',
|
||||
suggest: [
|
||||
{
|
||||
messageId: 'putExposeAtTheLast',
|
||||
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]
|
||||
)
|
||||
|
||||
return [
|
||||
fixer.insertTextAfter(target, textNode),
|
||||
fixer.removeRange([cutStart, cutEnd])
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Move all lines of "node" with its comments to before the "target"
|
||||
* @param {RuleFixer} fixer
|
||||
* @param {ASTNode} node
|
||||
* @param {ASTNode} target
|
||||
*/
|
||||
function moveNodeBefore(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
|
||||
})
|
||||
// get positions of what we need to remove
|
||||
const cutStart = getLineStartIndex(nodeComment, beforeNodeToken)
|
||||
const cutEnd = getLineStartIndex(nextNodeComment, node)
|
||||
// get space before target
|
||||
const beforeTargetToken = sourceCode.getTokenBefore(target)
|
||||
const targetComment = sourceCode.getTokenAfter(beforeTargetToken, {
|
||||
includeComments: true
|
||||
})
|
||||
// make insert text: comments + node + space before target
|
||||
const textNode = sourceCode.getText(
|
||||
node,
|
||||
node.range[0] - nodeComment.range[0]
|
||||
)
|
||||
const insertText = getInsertText(textNode, target)
|
||||
|
||||
return [
|
||||
fixer.insertTextBefore(targetComment, insertText),
|
||||
fixer.removeRange([cutStart, cutEnd])
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Get result text to insert
|
||||
* @param {string} textNode
|
||||
* @param {ASTNode} target
|
||||
*/
|
||||
function getInsertText(textNode, target) {
|
||||
const afterTargetComment = sourceCode.getTokenAfter(target, {
|
||||
includeComments: true
|
||||
})
|
||||
const afterText = sourceCode.text.slice(
|
||||
target.range[1],
|
||||
afterTargetComment.range[0]
|
||||
)
|
||||
// handle case when a();b() -> b()a();
|
||||
const invalidResult = !textNode.endsWith(';') && !afterText.includes('\n')
|
||||
|
||||
return textNode + afterText + (invalidResult ? ';' : '')
|
||||
}
|
||||
|
||||
/**
|
||||
* Get position of the beginning of the token's line(or prevToken end if no line)
|
||||
* @param {ASTNode|Token} token
|
||||
* @param {ASTNode|Token} prevToken
|
||||
*/
|
||||
function getLineStartIndex(token, prevToken) {
|
||||
// if we have next token on the same line - get index right before that token
|
||||
if (token.loc.start.line === prevToken.loc.end.line) {
|
||||
return prevToken.range[1]
|
||||
}
|
||||
|
||||
return sourceCode.getIndexFromLoc({
|
||||
line: token.loc.start.line,
|
||||
column: 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'layout',
|
||||
docs: {
|
||||
description:
|
||||
'enforce order of `defineEmits` and `defineProps` compiler macros',
|
||||
categories: undefined,
|
||||
url: 'https://eslint.vuejs.org/rules/define-macros-order.html'
|
||||
},
|
||||
fixable: 'code',
|
||||
hasSuggestions: true,
|
||||
schema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
order: {
|
||||
type: 'array',
|
||||
items: {
|
||||
enum: ORDER_SCHEMA
|
||||
},
|
||||
uniqueItems: true,
|
||||
additionalItems: false
|
||||
},
|
||||
defineExposeLast: {
|
||||
type: 'boolean'
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
],
|
||||
messages: {
|
||||
macrosNotOnTop:
|
||||
'{{macro}} should be the first statement in `<script setup>` (after any potential import statements or type definitions).',
|
||||
defineExposeNotTheLast:
|
||||
'`defineExpose` should be the last statement in `<script setup>`.',
|
||||
putExposeAtTheLast:
|
||||
'Put `defineExpose` as the last statement in `<script setup>`.'
|
||||
}
|
||||
},
|
||||
create
|
||||
}
|
Reference in New Issue
Block a user