eslint-plugin-vue
Advanced tools
Comparing version 7.0.0-alpha.5 to 7.0.0-alpha.6
@@ -16,2 +16,3 @@ /* | ||
'vue/dot-location': 'off', | ||
'vue/func-call-spacing': 'off', | ||
'vue/html-closing-bracket-newline': 'off', | ||
@@ -34,3 +35,6 @@ 'vue/html-closing-bracket-spacing': 'off', | ||
'vue/no-spaces-around-equal-signs-in-attribute': 'off', | ||
'vue/object-curly-newline': 'off', | ||
'vue/object-curly-spacing': 'off', | ||
'vue/object-property-newline': 'off', | ||
'vue/operator-linebreak': 'off', | ||
'vue/padding-line-between-blocks': 'off', | ||
@@ -37,0 +41,0 @@ 'vue/script-indent': 'off', |
@@ -11,2 +11,3 @@ /* | ||
'vue/component-tags-order': 'warn', | ||
'vue/no-multiple-slot-args': 'warn', | ||
'vue/no-v-html': 'warn', | ||
@@ -13,0 +14,0 @@ 'vue/order-in-components': 'warn', |
@@ -14,2 +14,3 @@ /* | ||
'vue/no-deprecated-dollar-listeners-api': 'error', | ||
'vue/no-deprecated-dollar-scopedslots-api': 'error', | ||
'vue/no-deprecated-events-api': 'error', | ||
@@ -46,2 +47,3 @@ 'vue/no-deprecated-filter': 'error', | ||
'vue/require-render-return': 'error', | ||
'vue/require-slots-as-functions': 'error', | ||
'vue/require-toggle-inside-transition': 'error', | ||
@@ -48,0 +50,0 @@ 'vue/require-v-for-key': 'error', |
@@ -11,2 +11,3 @@ /* | ||
'vue/component-tags-order': 'warn', | ||
'vue/no-multiple-slot-args': 'warn', | ||
'vue/no-v-html': 'warn', | ||
@@ -13,0 +14,0 @@ 'vue/order-in-components': 'warn', |
@@ -28,2 +28,3 @@ /* | ||
eqeqeq: require('./rules/eqeqeq'), | ||
'func-call-spacing': require('./rules/func-call-spacing'), | ||
'html-closing-bracket-newline': require('./rules/html-closing-bracket-newline'), | ||
@@ -49,2 +50,3 @@ 'html-closing-bracket-spacing': require('./rules/html-closing-bracket-spacing'), | ||
'no-async-in-computed-properties': require('./rules/no-async-in-computed-properties'), | ||
'no-bare-strings-in-template': require('./rules/no-bare-strings-in-template'), | ||
'no-boolean-default': require('./rules/no-boolean-default'), | ||
@@ -55,2 +57,3 @@ 'no-confusing-v-for-v-if': require('./rules/no-confusing-v-for-v-if'), | ||
'no-deprecated-dollar-listeners-api': require('./rules/no-deprecated-dollar-listeners-api'), | ||
'no-deprecated-dollar-scopedslots-api': require('./rules/no-deprecated-dollar-scopedslots-api'), | ||
'no-deprecated-events-api': require('./rules/no-deprecated-events-api'), | ||
@@ -76,2 +79,3 @@ 'no-deprecated-filter': require('./rules/no-deprecated-filter'), | ||
'no-multi-spaces': require('./rules/no-multi-spaces'), | ||
'no-multiple-slot-args': require('./rules/no-multiple-slot-args'), | ||
'no-multiple-template-root': require('./rules/no-multiple-template-root'), | ||
@@ -84,3 +88,5 @@ 'no-mutating-props': require('./rules/no-mutating-props'), | ||
'no-reserved-keys': require('./rules/no-reserved-keys'), | ||
'no-restricted-static-attribute': require('./rules/no-restricted-static-attribute'), | ||
'no-restricted-syntax': require('./rules/no-restricted-syntax'), | ||
'no-restricted-v-bind': require('./rules/no-restricted-v-bind'), | ||
'no-setup-props-destructure': require('./rules/no-setup-props-destructure'), | ||
@@ -102,7 +108,12 @@ 'no-shared-component-data': require('./rules/no-shared-component-data'), | ||
'no-useless-concat': require('./rules/no-useless-concat'), | ||
'no-useless-mustaches': require('./rules/no-useless-mustaches'), | ||
'no-useless-v-bind': require('./rules/no-useless-v-bind'), | ||
'no-v-html': require('./rules/no-v-html'), | ||
'no-v-model-argument': require('./rules/no-v-model-argument'), | ||
'no-watch-after-await': require('./rules/no-watch-after-await'), | ||
'object-curly-newline': require('./rules/object-curly-newline'), | ||
'object-curly-spacing': require('./rules/object-curly-spacing'), | ||
'object-property-newline': require('./rules/object-property-newline'), | ||
'one-component-per-file': require('./rules/one-component-per-file'), | ||
'operator-linebreak': require('./rules/operator-linebreak'), | ||
'order-in-components': require('./rules/order-in-components'), | ||
@@ -120,2 +131,3 @@ 'padding-line-between-blocks': require('./rules/padding-line-between-blocks'), | ||
'require-render-return': require('./rules/require-render-return'), | ||
'require-slots-as-functions': require('./rules/require-slots-as-functions'), | ||
'require-toggle-inside-transition': require('./rules/require-toggle-inside-transition'), | ||
@@ -122,0 +134,0 @@ 'require-v-for-key': require('./rules/require-v-for-key'), |
@@ -13,3 +13,3 @@ /** | ||
const DEFAULT_ORDER = Object.freeze(['script', 'template', 'style']) | ||
const DEFAULT_ORDER = Object.freeze([['script', 'template'], 'style']) | ||
@@ -29,10 +29,20 @@ // ------------------------------------------------------------------------------ | ||
fixable: null, | ||
schema: { | ||
type: 'array', | ||
properties: { | ||
order: { | ||
type: 'array' | ||
schema: [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
order: { | ||
type: 'array', | ||
items: { | ||
anyOf: [ | ||
{ type: 'string' }, | ||
{ type: 'array', items: { type: 'string' }, uniqueItems: true } | ||
] | ||
}, | ||
uniqueItems: true, | ||
additionalItems: false | ||
} | ||
} | ||
} | ||
}, | ||
], | ||
messages: { | ||
@@ -44,4 +54,16 @@ unexpected: | ||
create(context) { | ||
const order = | ||
(context.options[0] && context.options[0].order) || DEFAULT_ORDER | ||
/** @type {Map<string, number} */ | ||
const orderMap = new Map() | ||
;( | ||
(context.options[0] && context.options[0].order) || | ||
DEFAULT_ORDER | ||
).forEach((nameOrNames, index) => { | ||
if (Array.isArray(nameOrNames)) { | ||
for (const name of nameOrNames) { | ||
orderMap.set(name, index) | ||
} | ||
} else { | ||
orderMap.set(nameOrNames, index) | ||
} | ||
}) | ||
const documentFragment = | ||
@@ -82,3 +104,3 @@ context.parserServices.getDocumentFragment && | ||
elements.forEach((element, index) => { | ||
const expectedIndex = order.indexOf(element.name) | ||
const expectedIndex = orderMap.get(element.name) | ||
if (expectedIndex < 0) { | ||
@@ -89,5 +111,5 @@ return | ||
.slice(0, index) | ||
.filter((e) => expectedIndex < order.indexOf(e.name)) | ||
.filter((e) => expectedIndex < orderMap.get(e.name)) | ||
.sort( | ||
(e1, e2) => order.indexOf(e1.name) - order.indexOf(e2.name) | ||
(e1, e2) => orderMap.get(e1.name) - orderMap.get(e2.name) | ||
)[0] | ||
@@ -94,0 +116,0 @@ if (firstUnordered) { |
@@ -7,2 +7,3 @@ /** | ||
const { ReferenceTracker, findVariable } = require('eslint-utils') | ||
const utils = require('../utils') | ||
@@ -22,3 +23,3 @@ module.exports = { | ||
requireDotValue: | ||
'Must use `.value` to read or write the value wrapped by `ref()`.' | ||
'Must use `.value` to read or write the value wrapped by `{{method}}()`.' | ||
} | ||
@@ -30,3 +31,4 @@ }, | ||
function reportIfRefWrapped(node) { | ||
if (!refReferenceIds.has(node)) { | ||
const data = refReferenceIds.get(node) | ||
if (!data) { | ||
return | ||
@@ -36,3 +38,6 @@ } | ||
node, | ||
messageId: 'requireDotValue' | ||
messageId: 'requireDotValue', | ||
data: { | ||
method: data.method | ||
} | ||
}) | ||
@@ -48,2 +53,14 @@ } | ||
[ReferenceTracker.CALL]: true | ||
}, | ||
computed: { | ||
[ReferenceTracker.CALL]: true | ||
}, | ||
toRef: { | ||
[ReferenceTracker.CALL]: true | ||
}, | ||
customRef: { | ||
[ReferenceTracker.CALL]: true | ||
}, | ||
shallowRef: { | ||
[ReferenceTracker.CALL]: true | ||
} | ||
@@ -53,3 +70,3 @@ } | ||
for (const { node } of tracker.iterateEsmReferences(traceMap)) { | ||
for (const { node, path } of tracker.iterateEsmReferences(traceMap)) { | ||
const variableDeclarator = node.parent | ||
@@ -82,3 +99,4 @@ if ( | ||
variableDeclarator, | ||
variableDeclaration | ||
variableDeclaration, | ||
method: path[1] | ||
}) | ||
@@ -118,9 +136,9 @@ } | ||
// Report only constants. | ||
const info = refReferenceIds.get(node) | ||
if (!info) { | ||
const data = refReferenceIds.get(node) | ||
if (!data) { | ||
return | ||
} | ||
if ( | ||
!info.variableDeclaration || | ||
info.variableDeclaration.kind !== 'const' | ||
!data.variableDeclaration || | ||
data.variableDeclaration.kind !== 'const' | ||
) { | ||
@@ -137,2 +155,22 @@ return | ||
reportIfRefWrapped(node) | ||
}, | ||
// `${refValue}` | ||
'TemplateLiteral>Identifier'(node) { | ||
reportIfRefWrapped(node) | ||
}, | ||
// refValue.x | ||
'MemberExpression>Identifier'(node) { | ||
if (node.parent.object !== node) { | ||
return | ||
} | ||
const name = utils.getStaticPropertyName(node.parent) | ||
if ( | ||
name === 'value' || | ||
name == null || | ||
// WritableComputedRef | ||
name === 'effect' | ||
) { | ||
return | ||
} | ||
reportIfRefWrapped(node) | ||
} | ||
@@ -139,0 +177,0 @@ } |
@@ -19,6 +19,11 @@ /** | ||
* @typedef {import('vue-eslint-parser').AST.ESLintIdentifier} Identifier | ||
* @typedef {import('vue-eslint-parser').AST.ESLintPattern} Pattern | ||
* @typedef {import('vue-eslint-parser').AST.ESLintThisExpression} ThisExpression | ||
* @typedef {import('vue-eslint-parser').AST.ESLintMemberExpression} MemberExpression | ||
* @typedef {import('vue-eslint-parser').AST.ESLintFunctionExpression} FunctionExpression | ||
* @typedef {import('vue-eslint-parser').AST.ESLintArrowFunctionExpression} ArrowFunctionExpression | ||
* @typedef {import('vue-eslint-parser').AST.ESLintFunctionDeclaration} FunctionDeclaration | ||
* @typedef {import('vue-eslint-parser').AST.VAttribute} VAttribute | ||
* @typedef {import('vue-eslint-parser').AST.VIdentifier} VIdentifier | ||
* @typedef {import('vue-eslint-parser').AST.VExpressionContainer} VExpressionContainer | ||
* @typedef {import('eslint').Scope.Variable} Variable | ||
@@ -28,7 +33,17 @@ * @typedef {import('eslint').Rule.RuleContext} RuleContext | ||
/** | ||
* @typedef { { name: string, groupName: string, node: ASTNode } } PropertyData | ||
* @typedef { { usedNames: Set<string> } } TemplatePropertiesContainer | ||
* @typedef { { properties: Array<PropertyData>, usedNames: Set<string>, unknown: boolean, usedPropsNames: Set<string>, unknownProps: boolean } } VueComponentPropertiesContainer | ||
* @typedef {import('../utils').ComponentPropertyData} ComponentPropertyData | ||
*/ | ||
/** | ||
* @typedef {object} TemplatePropertiesContainer | ||
* @property {Set<string>} usedNames | ||
* @property {Set<string>} refNames | ||
* @typedef {object} VueComponentPropertiesContainer | ||
* @property {ComponentPropertyData[]} properties | ||
* @property {Set<string>} usedNames | ||
* @property {boolean} unknown | ||
* @property {Set<string>} usedPropsNames | ||
* @property {boolean} unknownProps | ||
* @typedef { { node: FunctionExpression | ArrowFunctionExpression | FunctionDeclaration, index: number } } CallIdAndParamIndex | ||
* @typedef { { usedNames: Set<string>, unknown: boolean } } UsedProperties | ||
* @typedef { { usedNames: UsedNames, unknown: boolean } } UsedProperties | ||
* @typedef { (context: RuleContext) => UsedProps } UsedPropsTracker | ||
*/ | ||
@@ -105,2 +120,47 @@ | ||
class UsedNames { | ||
constructor() { | ||
/** @type {Map<string, UsedPropsTracker[]>} */ | ||
this.map = new Map() | ||
} | ||
/** | ||
* @returns {IterableIterator<string>} | ||
*/ | ||
names() { | ||
return this.map.keys() | ||
} | ||
/** | ||
* @param {string} name | ||
* @returns {UsedPropsTracker[]} | ||
*/ | ||
get(name) { | ||
return this.map.get(name) || [] | ||
} | ||
/** | ||
* @param {string} name | ||
* @param {UsedPropsTracker} tracker | ||
*/ | ||
add(name, tracker) { | ||
const list = this.map.get(name) | ||
if (list) { | ||
list.push(tracker) | ||
} else { | ||
this.map.set(name, [tracker]) | ||
} | ||
} | ||
/** | ||
* @param {UsedNames} other | ||
*/ | ||
addAll(other) { | ||
other.map.forEach((trackers, name) => { | ||
const list = this.map.get(name) | ||
if (list) { | ||
list.push(...trackers) | ||
} else { | ||
this.map.set(name, trackers) | ||
} | ||
}) | ||
} | ||
} | ||
/** | ||
@@ -111,6 +171,7 @@ * @param {ObjectPattern} node | ||
function extractObjectPatternProperties(node) { | ||
const usedNames = new Set() | ||
const usedNames = new UsedNames() | ||
for (const prop of node.properties) { | ||
if (prop.type === 'Property') { | ||
usedNames.add(utils.getStaticPropertyName(prop)) | ||
const name = utils.getStaticPropertyName(prop) | ||
usedNames.add(name, getObjectPatternPropertyPatternTracker(prop.value)) | ||
} else { | ||
@@ -131,8 +192,52 @@ // If use RestElement, everything is used! | ||
/** | ||
* @param {Identifier | ThisExpression} node | ||
* @param {Pattern} pattern | ||
* @returns {UsedPropsTracker} | ||
*/ | ||
function getObjectPatternPropertyPatternTracker(pattern) { | ||
if (pattern.type === 'ObjectPattern') { | ||
return () => { | ||
const result = new UsedProps() | ||
const { usedNames, unknown } = extractObjectPatternProperties(pattern) | ||
result.usedNames.addAll(usedNames) | ||
result.unknown = unknown | ||
return result | ||
} | ||
} | ||
if (pattern.type === 'Identifier') { | ||
return (context) => { | ||
const result = new UsedProps() | ||
const variable = findVariable(context, pattern) | ||
if (!variable) { | ||
return result | ||
} | ||
for (const reference of variable.references) { | ||
/** @type {Identifier} */ | ||
// @ts-ignore | ||
const id = reference.identifier | ||
const { usedNames, unknown, calls } = extractPatternOrThisProperties( | ||
id, | ||
context | ||
) | ||
result.usedNames.addAll(usedNames) | ||
result.unknown = result.unknown || unknown | ||
result.calls.push(...calls) | ||
} | ||
return result | ||
} | ||
} else if (pattern.type === 'AssignmentPattern') { | ||
return getObjectPatternPropertyPatternTracker(pattern.left) | ||
} | ||
return () => { | ||
const result = new UsedProps() | ||
result.unknown = true | ||
return result | ||
} | ||
} | ||
/** | ||
* @param {Identifier | MemberExpression | ThisExpression} node | ||
* @param {RuleContext} context | ||
* @returns {UsedProps} | ||
*/ | ||
function extractIdOrThisProperties(node, context) { | ||
/** @type {UsedProps} */ | ||
function extractPatternOrThisProperties(node, context) { | ||
const result = new UsedProps() | ||
@@ -144,12 +249,34 @@ const parent = node.parent | ||
const { usedNames, unknown } = extractObjectPatternProperties(parent.left) | ||
usedNames.forEach((name) => result.usedNames.add(name)) | ||
result.usedNames.addAll(usedNames) | ||
result.unknown = result.unknown || unknown | ||
} | ||
return result | ||
} else if (parent.type === 'VariableDeclarator') { | ||
if (parent.init === node && parent.id.type === 'ObjectPattern') { | ||
// `const {foo} = arg` | ||
const { usedNames, unknown } = extractObjectPatternProperties(parent.id) | ||
usedNames.forEach((name) => result.usedNames.add(name)) | ||
result.unknown = result.unknown || unknown | ||
if (parent.init === node) { | ||
if (parent.id.type === 'ObjectPattern') { | ||
// `const {foo} = arg` | ||
const { usedNames, unknown } = extractObjectPatternProperties(parent.id) | ||
result.usedNames.addAll(usedNames) | ||
result.unknown = result.unknown || unknown | ||
} else if (parent.id.type === 'Identifier') { | ||
// `const foo = arg` | ||
const variable = findVariable(context, parent.id) | ||
if (!variable) { | ||
return result | ||
} | ||
for (const reference of variable.references) { | ||
/** @type {Identifier} */ | ||
// @ts-ignore | ||
const id = reference.identifier | ||
const { usedNames, unknown, calls } = extractPatternOrThisProperties( | ||
id, | ||
context | ||
) | ||
result.usedNames.addAll(usedNames) | ||
result.unknown = result.unknown || unknown | ||
result.calls.push(...calls) | ||
} | ||
} | ||
} | ||
return result | ||
} else if (parent.type === 'MemberExpression') { | ||
@@ -160,3 +287,5 @@ if (parent.object === node) { | ||
if (name) { | ||
result.usedNames.add(name) | ||
result.usedNames.add(name, () => | ||
extractPatternOrThisProperties(parent, context) | ||
) | ||
} else { | ||
@@ -166,3 +295,5 @@ result.unknown = true | ||
} | ||
return result | ||
} else if (parent.type === 'CallExpression') { | ||
// @ts-ignore | ||
const argIndex = parent.arguments.indexOf(node) | ||
@@ -206,4 +337,3 @@ if (argIndex > -1 && parent.callee.type === 'Identifier') { | ||
constructor() { | ||
/** @type {Set<string>} */ | ||
this.usedNames = new Set() | ||
this.usedNames = new UsedNames() | ||
/** @type {CallIdAndParamIndex[]} */ | ||
@@ -232,3 +362,3 @@ this.calls = [] | ||
const { usedNames, unknown } = extractObjectPatternProperties(paramNode) | ||
usedNames.forEach((name) => this.usedNames.add(name)) | ||
this.usedNames.addAll(usedNames) | ||
this.unknown = this.unknown || unknown | ||
@@ -245,7 +375,7 @@ return | ||
const id = reference.identifier | ||
const { usedNames, unknown, calls } = extractIdOrThisProperties( | ||
const { usedNames, unknown, calls } = extractPatternOrThisProperties( | ||
id, | ||
context | ||
) | ||
usedNames.forEach((name) => this.usedNames.add(name)) | ||
this.usedNames.addAll(usedNames) | ||
this.unknown = this.unknown || unknown | ||
@@ -339,3 +469,4 @@ this.calls.push(...calls) | ||
const templatePropertiesContainer = { | ||
usedNames: new Set() | ||
usedNames: new Set(), | ||
refNames: new Set() | ||
} | ||
@@ -383,2 +514,3 @@ /** @type {Map<ASTNode, VueComponentPropertiesContainer>} */ | ||
if (container.unknown) { | ||
// unknown | ||
continue | ||
@@ -391,2 +523,3 @@ } | ||
) { | ||
// used | ||
continue | ||
@@ -399,4 +532,12 @@ } | ||
) { | ||
// used props | ||
continue | ||
} | ||
if ( | ||
property.groupName === 'setup' && | ||
templatePropertiesContainer.refNames.has(property.name) | ||
) { | ||
// used template refs | ||
continue | ||
} | ||
context.report({ | ||
@@ -417,3 +558,3 @@ node: property.node, | ||
* @param {Map<ASTNode,Set<number>>} already | ||
* @returns {Generator<UsedProps>} | ||
* @returns {IterableIterator<UsedProps>} | ||
*/ | ||
@@ -442,2 +583,18 @@ function* iterateUsedProps(usedProps, already = new Map()) { | ||
/** | ||
* @param {VueComponentPropertiesContainer} container | ||
* @param {UsedProps} baseUseProps | ||
*/ | ||
function processParamPropsUsed(container, baseUseProps) { | ||
for (const { usedNames, unknown } of iterateUsedProps(baseUseProps)) { | ||
if (unknown) { | ||
container.unknownProps = true | ||
return | ||
} | ||
for (const name of usedNames.names()) { | ||
container.usedPropsNames.add(name) | ||
} | ||
} | ||
} | ||
const scriptVisitor = Object.assign( | ||
@@ -468,19 +625,41 @@ {}, | ||
const container = getVueComponentPropertiesContainer(vueData.node) | ||
const propsParam = node.params[0] | ||
if (!propsParam) { | ||
// no arguments | ||
return | ||
if (node.params[0]) { | ||
const paramsUsedProps = getParamsUsedProps(node) | ||
const paramUsedProps = paramsUsedProps.getParam(0) | ||
processParamPropsUsed(container, paramUsedProps) | ||
} | ||
const paramsUsedProps = getParamsUsedProps(node) | ||
const paramUsedProps = paramsUsedProps.getParam(0) | ||
}, | ||
onRenderFunctionEnter(node, vueData) { | ||
const container = getVueComponentPropertiesContainer(vueData.node) | ||
if (node.params[0]) { | ||
// for Vue 3.x render | ||
const paramsUsedProps = getParamsUsedProps(node) | ||
const paramUsedProps = paramsUsedProps.getParam(0) | ||
for (const { usedNames, unknown } of iterateUsedProps( | ||
paramUsedProps | ||
)) { | ||
if (unknown) { | ||
container.unknownProps = true | ||
processParamPropsUsed(container, paramUsedProps) | ||
if (container.unknownProps) { | ||
return | ||
} | ||
for (const name of usedNames) { | ||
container.usedPropsNames.add(name) | ||
} | ||
if (vueData.functional && node.params[1]) { | ||
// for Vue 2.x render & functional | ||
const paramsUsedProps = getParamsUsedProps(node) | ||
const paramUsedProps = paramsUsedProps.getParam(1) | ||
for (const { usedNames, unknown } of iterateUsedProps( | ||
paramUsedProps | ||
)) { | ||
if (unknown) { | ||
container.unknownProps = true | ||
return | ||
} | ||
for (const usedPropsTracker of usedNames.get('props')) { | ||
const propUsedProps = usedPropsTracker(context) | ||
processParamPropsUsed(container, propUsedProps) | ||
if (container.unknownProps) { | ||
return | ||
} | ||
} | ||
} | ||
@@ -494,3 +673,3 @@ } | ||
const container = getVueComponentPropertiesContainer(vueData.node) | ||
const usedProps = extractIdOrThisProperties(node, context) | ||
const usedProps = extractPatternOrThisProperties(node, context) | ||
@@ -502,3 +681,3 @@ for (const { usedNames, unknown } of iterateUsedProps(usedProps)) { | ||
} | ||
for (const name of usedNames) { | ||
for (const name of usedNames.names()) { | ||
container.usedNames.add(name) | ||
@@ -519,2 +698,5 @@ } | ||
const templateVisitor = { | ||
/** | ||
* @param {VExpressionContainer} node | ||
*/ | ||
VExpressionContainer(node) { | ||
@@ -525,2 +707,10 @@ for (const name of getReferencesNames(node.references)) { | ||
}, | ||
/** | ||
* @param {VAttribute} node | ||
*/ | ||
'VAttribute[directive=false]'(node) { | ||
if (node.key.name === 'ref' && node.value != null) { | ||
templatePropertiesContainer.refNames.add(node.value.value) | ||
} | ||
}, | ||
"VElement[parent.type!='VElement']:exit"() { | ||
@@ -527,0 +717,0 @@ reportUnusedProperties() |
@@ -11,21 +11,50 @@ /** | ||
const defaultOrder = [ | ||
// Side Effects (triggers effects outside the component) | ||
'el', | ||
// Global Awareness (requires knowledge beyond the component) | ||
'name', | ||
'parent', | ||
// Component Type (changes the type of the component) | ||
'functional', | ||
// Template Modifiers (changes the way templates are compiled) | ||
['delimiters', 'comments'], | ||
// Template Dependencies (assets used in the template) | ||
['components', 'directives', 'filters'], | ||
// Composition (merges properties into the options) | ||
'extends', | ||
'mixins', | ||
['provide', 'inject'], // for Vue.js 2.2.0+ | ||
// Interface (the interface to the component) | ||
'inheritAttrs', | ||
'model', | ||
['props', 'propsData'], | ||
'fetch', | ||
'asyncData', | ||
'emits', // for Vue.js 3.x | ||
// Note: | ||
// The `setup` option is included in the "Composition" category, | ||
// but the behavior of the `setup` option requires the definition of "Interface", | ||
// so we prefer to put the `setup` option after the "Interface". | ||
'setup', // for Vue 3.x | ||
// Local State (local reactive properties) | ||
'fetch', // for Nuxt | ||
'asyncData', // for Nuxt | ||
'data', | ||
'computed', | ||
// Events (callbacks triggered by reactive events) | ||
'watch', | ||
'LIFECYCLE_HOOKS', | ||
// Non-Reactive Properties (instance properties independent of the reactivity system) | ||
'methods', | ||
'head', | ||
// Rendering (the declarative description of the component output) | ||
'head', // for Nuxt | ||
['template', 'render'], | ||
@@ -45,4 +74,9 @@ 'renderError' | ||
'deactivated', | ||
'beforeUnmount', // for Vue.js 3.x | ||
'unmounted', // for Vue.js 3.x | ||
'beforeDestroy', | ||
'destroyed' | ||
'destroyed', | ||
'renderTracked', // for Vue.js 3.x | ||
'renderTriggered', // for Vue.js 3.x | ||
'errorCaptured' // for Vue.js 2.5.0+ | ||
] | ||
@@ -69,3 +103,3 @@ } | ||
const ARITHMETIC_OPERATORS = ['+', '-', '*', '/', '%', '**'] | ||
const ARITHMETIC_OPERATORS = ['+', '-', '*', '/', '%', '**'/* es2016 */] | ||
const BITWISE_OPERATORS = ['&', '|', '^', '~', '<<', '>>', '>>>'] | ||
@@ -80,3 +114,3 @@ const COMPARISON_OPERATORS = ['==', '!=', '===', '!==', '>', '>=', '<', '<='] | ||
) | ||
const LOGICAL_OPERATORS = ['&&', '||'] | ||
const LOGICAL_OPERATORS = ['&&', '||', '??'/* es2020 */] | ||
@@ -101,7 +135,7 @@ /* | ||
let result = true | ||
const noSideEffectsNodes = new Set() | ||
let skipNode = false | ||
traverseNodes(node, { | ||
visitorKeys, | ||
enterNode (node, parent) { | ||
if (!result) { | ||
enterNode (node) { | ||
if (!result || skipNode) { | ||
return | ||
@@ -111,4 +145,2 @@ } | ||
if ( | ||
// parent has no side effects | ||
noSideEffectsNodes.has(parent) || | ||
// no side effects node | ||
@@ -122,3 +154,3 @@ node.type === 'FunctionExpression' || | ||
) { | ||
noSideEffectsNodes.add(node) | ||
skipNode = node | ||
} else if ( | ||
@@ -141,3 +173,7 @@ node.type !== 'Property' && | ||
}, | ||
leaveNode () {} | ||
leaveNode (node) { | ||
if (skipNode === node) { | ||
skipNode = null | ||
} | ||
} | ||
}) | ||
@@ -144,0 +180,0 @@ |
@@ -9,2 +9,9 @@ /** | ||
/** | ||
* @typedef {import('vue-eslint-parser').AST.ESLintExportDefaultDeclaration} ExportDefaultDeclaration | ||
* @typedef {import('vue-eslint-parser').AST.ESLintDeclaration} Declaration | ||
* @typedef {import('vue-eslint-parser').AST.ESLintExpression} Expression | ||
* @typedef {import('vue-eslint-parser').AST.ESLintReturnStatement} ReturnStatement | ||
* | ||
*/ | ||
// ------------------------------------------------------------------------------ | ||
@@ -23,3 +30,9 @@ // Rule Definition | ||
fixable: null, // or "code" or "whitespace" | ||
schema: [] | ||
schema: [{ | ||
type: 'object', | ||
properties: { | ||
disallowFunctionalComponentFunction: { type: 'boolean' } | ||
}, | ||
additionalProperties: false | ||
}] | ||
}, | ||
@@ -29,21 +42,71 @@ | ||
const filePath = context.getFilename() | ||
if (!utils.isVueFile(filePath)) return {} | ||
return { | ||
'ExportDefaultDeclaration:exit' (node) { | ||
if (!utils.isVueFile(filePath)) return | ||
const disallowFunctional = (context.options[0] || {}).disallowFunctionalComponentFunction | ||
const isObjectExpression = ( | ||
node.type === 'ExportDefaultDeclaration' && | ||
node.declaration.type === 'ObjectExpression' | ||
) | ||
let maybeVue3Functional | ||
let scopeStack = null | ||
if (!isObjectExpression) { | ||
context.report({ | ||
node, | ||
message: `Expected the component literal to be directly exported.` | ||
}) | ||
return { | ||
/** @param {Declaration | Expression} node */ | ||
'ExportDefaultDeclaration > *' (node) { | ||
if (node.type === 'ObjectExpression') { | ||
// OK | ||
return | ||
} | ||
} | ||
if (!disallowFunctional) { | ||
if (node.type === 'ArrowFunctionExpression') { | ||
if (node.body.type !== 'BlockStatement') { | ||
// OK | ||
return | ||
} | ||
maybeVue3Functional = { | ||
body: node.body | ||
} | ||
return | ||
} | ||
if (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration') { | ||
maybeVue3Functional = { | ||
body: node.body | ||
} | ||
return | ||
} | ||
} | ||
context.report({ | ||
node: node.parent, | ||
message: `Expected the component literal to be directly exported.` | ||
}) | ||
}, | ||
...(disallowFunctional ? {} : { | ||
':function > BlockStatement' (node) { | ||
if (!maybeVue3Functional) { | ||
return | ||
} | ||
scopeStack = { upper: scopeStack, withinVue3FunctionalBody: maybeVue3Functional.body === node } | ||
}, | ||
/** @param {ReturnStatement} node */ | ||
ReturnStatement (node) { | ||
if (scopeStack && scopeStack.withinVue3FunctionalBody && node.argument) { | ||
maybeVue3Functional.hasReturnArgument = true | ||
} | ||
}, | ||
':function > BlockStatement:exit' (node) { | ||
scopeStack = scopeStack && scopeStack.upper | ||
}, | ||
/** @param {ExportDefaultDeclaration} node */ | ||
'ExportDefaultDeclaration:exit' (node) { | ||
if (!maybeVue3Functional) { | ||
return | ||
} | ||
if (!maybeVue3Functional.hasReturnArgument) { | ||
context.report({ | ||
node, | ||
message: `Expected the component literal to be directly exported.` | ||
}) | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
} |
@@ -27,4 +27,2 @@ /** | ||
const FIX_EMITS_AFTER_OPTIONS = [ | ||
'props', | ||
'propsData', | ||
'setup', | ||
@@ -48,4 +46,9 @@ 'data', | ||
'deactivated', | ||
'beforeUnmount', | ||
'unmounted', | ||
'beforeDestroy', | ||
'destroyed' | ||
'destroyed', | ||
'renderTracked', | ||
'renderTriggered', | ||
'errorCaptured' | ||
] | ||
@@ -52,0 +55,0 @@ |
@@ -40,6 +40,3 @@ /** | ||
function isLhs(node) { | ||
return ( | ||
Boolean(node) && | ||
(node.type === 'Identifier' || node.type === 'MemberExpression') | ||
) | ||
return node.type === 'Identifier' || node.type === 'MemberExpression' | ||
} | ||
@@ -89,26 +86,28 @@ | ||
if (node.value) { | ||
if (!isLhs(node.value.expression)) { | ||
if (!node.value || !node.value.expression) { | ||
return | ||
} | ||
if (!isLhs(node.value.expression)) { | ||
context.report({ | ||
node, | ||
loc: node.loc, | ||
messageId: 'unexpectedNonLhsExpression' | ||
}) | ||
} | ||
for (const reference of node.value.references) { | ||
const id = reference.id | ||
if (id.parent.type !== 'VExpressionContainer') { | ||
continue | ||
} | ||
const variable = reference.variable | ||
if (variable) { | ||
context.report({ | ||
node, | ||
loc: node.loc, | ||
messageId: 'unexpectedNonLhsExpression' | ||
messageId: 'unexpectedUpdateIterationVariable', | ||
data: { varName: id.name } | ||
}) | ||
} | ||
for (const reference of node.value.references) { | ||
const id = reference.id | ||
if (id.parent.type !== 'VExpressionContainer') { | ||
continue | ||
} | ||
const variable = reference.variable | ||
if (variable) { | ||
context.report({ | ||
node, | ||
loc: node.loc, | ||
messageId: 'unexpectedUpdateIterationVariable', | ||
data: { varName: id.name } | ||
}) | ||
} | ||
} | ||
} | ||
@@ -115,0 +114,0 @@ } |
@@ -51,3 +51,3 @@ /** | ||
if (!utils.hasAttributeValue(node)) { | ||
if (!node.value || utils.isEmptyValueDirective(node, context)) { | ||
context.report({ | ||
@@ -54,0 +54,0 @@ node, |
@@ -73,3 +73,3 @@ /** | ||
} | ||
if (!utils.hasAttributeValue(node)) { | ||
if (!node.value || utils.isEmptyValueDirective(node, context)) { | ||
context.report({ | ||
@@ -76,0 +76,0 @@ node, |
@@ -73,3 +73,3 @@ /** | ||
} | ||
if (utils.hasAttributeValue(node)) { | ||
if (node.value) { | ||
context.report({ | ||
@@ -76,0 +76,0 @@ node, |
@@ -140,3 +140,3 @@ /** | ||
} | ||
if (!utils.hasAttributeValue(node)) { | ||
if (!node.value || utils.isEmptyValueDirective(node, context)) { | ||
context.report({ | ||
@@ -143,0 +143,0 @@ node, |
@@ -47,3 +47,3 @@ /** | ||
} | ||
if (!utils.hasAttributeValue(node)) { | ||
if (!node.value || utils.isEmptyValueDirective(node, context)) { | ||
context.report({ | ||
@@ -50,0 +50,0 @@ node, |
@@ -65,3 +65,3 @@ /** | ||
} | ||
if (!utils.hasAttributeValue(node)) { | ||
if (!node.value || utils.isEmptyValueDirective(node, context)) { | ||
context.report({ | ||
@@ -68,0 +68,0 @@ node, |
@@ -45,6 +45,3 @@ /** | ||
function isLhs(node) { | ||
return ( | ||
node != null && | ||
(node.type === 'Identifier' || node.type === 'MemberExpression') | ||
) | ||
return node.type === 'Identifier' || node.type === 'MemberExpression' | ||
} | ||
@@ -137,3 +134,3 @@ | ||
if (!utils.hasAttributeValue(node)) { | ||
if (!node.value || utils.isEmptyValueDirective(node, context)) { | ||
context.report({ | ||
@@ -144,5 +141,25 @@ node, | ||
}) | ||
return | ||
} | ||
if (node.value) { | ||
if (!isLhs(node.value.expression)) { | ||
if (!node.value.expression) { | ||
// Parsing error | ||
return | ||
} | ||
if (!isLhs(node.value.expression)) { | ||
context.report({ | ||
node, | ||
loc: node.loc, | ||
message: | ||
"'v-model' directives require the attribute value which is valid as LHS." | ||
}) | ||
} | ||
for (const reference of node.value.references) { | ||
const id = reference.id | ||
if (id.parent.type !== 'VExpressionContainer') { | ||
continue | ||
} | ||
const variable = getVariable(id.name, element) | ||
if (variable != null) { | ||
context.report({ | ||
@@ -152,23 +169,6 @@ node, | ||
message: | ||
"'v-model' directives require the attribute value which is valid as LHS." | ||
"'v-model' directives cannot update the iteration variable '{{varName}}' itself.", | ||
data: { varName: id.name } | ||
}) | ||
} | ||
for (const reference of node.value.references) { | ||
const id = reference.id | ||
if (id.parent.type !== 'VExpressionContainer') { | ||
continue | ||
} | ||
const variable = getVariable(id.name, element) | ||
if (variable != null) { | ||
context.report({ | ||
node, | ||
loc: node.loc, | ||
message: | ||
"'v-model' directives cannot update the iteration variable '{{varName}}' itself.", | ||
data: { varName: id.name } | ||
}) | ||
} | ||
} | ||
} | ||
@@ -175,0 +175,0 @@ } |
@@ -111,3 +111,3 @@ /** | ||
if ( | ||
!utils.hasAttributeValue(node) && | ||
(!node.value || !node.value.expression) && | ||
!node.key.modifiers.some((modifier) => | ||
@@ -117,11 +117,21 @@ VERB_MODIFIERS.has(modifier.name) | ||
) { | ||
if (node.value && sourceCode.getText(node.value.expression)) { | ||
const value = sourceCode.getText(node.value) | ||
context.report({ | ||
node, | ||
loc: node.loc, | ||
message: | ||
'Avoid using JavaScript keyword as "v-on" value: {{value}}.', | ||
data: { value } | ||
}) | ||
if (node.value && !utils.isEmptyValueDirective(node, context)) { | ||
const valueText = sourceCode.getText(node.value) | ||
let innerText = valueText | ||
if ( | ||
(valueText[0] === '"' || valueText[0] === "'") && | ||
valueText[0] === valueText[valueText.length - 1] | ||
) { | ||
// quoted | ||
innerText = valueText.slice(1, -1) | ||
} | ||
if (/^\w+$/.test(innerText)) { | ||
context.report({ | ||
node, | ||
loc: node.loc, | ||
message: | ||
'Avoid using JavaScript keyword as "v-on" value: {{value}}.', | ||
data: { value: valueText } | ||
}) | ||
} | ||
} else { | ||
@@ -128,0 +138,0 @@ context.report({ |
@@ -47,3 +47,3 @@ /** | ||
} | ||
if (!utils.hasAttributeValue(node)) { | ||
if (!node.value || utils.isEmptyValueDirective(node, context)) { | ||
context.report({ | ||
@@ -50,0 +50,0 @@ node, |
@@ -267,3 +267,5 @@ /** | ||
isDefaultSlot && | ||
!utils.hasAttributeValue(node) | ||
(!node.value || | ||
utils.isEmptyValueDirective(node, context) || | ||
utils.isEmptyExpressionValueDirective(node, context)) | ||
) { | ||
@@ -270,0 +272,0 @@ context.report({ |
@@ -47,3 +47,3 @@ /** | ||
} | ||
if (!utils.hasAttributeValue(node)) { | ||
if (!node.value || utils.isEmptyValueDirective(node, context)) { | ||
context.report({ | ||
@@ -50,0 +50,0 @@ node, |
@@ -17,7 +17,94 @@ /** | ||
const KNOWN_NODES = new Set(['ArrayExpression', 'ArrayPattern', 'ArrowFunctionExpression', 'AssignmentExpression', 'AssignmentPattern', 'AwaitExpression', 'BinaryExpression', 'BlockStatement', 'BreakStatement', 'CallExpression', 'CatchClause', 'ClassBody', 'ClassDeclaration', 'ClassExpression', 'ConditionalExpression', 'ContinueStatement', 'DebuggerStatement', 'DoWhileStatement', 'EmptyStatement', 'ExperimentalRestProperty', 'ExperimentalSpreadProperty', 'ExportAllDeclaration', 'ExportDefaultDeclaration', 'ExportNamedDeclaration', 'ExportSpecifier', 'ExpressionStatement', 'ForInStatement', 'ForOfStatement', 'ForStatement', 'FunctionDeclaration', 'FunctionExpression', 'Identifier', 'IfStatement', 'ImportDeclaration', 'ImportDefaultSpecifier', 'ImportNamespaceSpecifier', 'ImportSpecifier', 'LabeledStatement', 'Literal', 'LogicalExpression', 'MemberExpression', 'MetaProperty', 'MethodDefinition', 'NewExpression', 'ObjectExpression', 'ObjectPattern', 'Program', 'Property', 'RestElement', 'ReturnStatement', 'SequenceExpression', 'SpreadElement', 'Super', 'SwitchCase', 'SwitchStatement', 'TaggedTemplateExpression', 'TemplateElement', 'TemplateLiteral', 'ThisExpression', 'ThrowStatement', 'TryStatement', 'UnaryExpression', 'UpdateExpression', 'VariableDeclaration', 'VariableDeclarator', 'WhileStatement', 'WithStatement', 'YieldExpression', 'VAttribute', 'VDirectiveKey', 'VDocumentFragment', 'VElement', 'VEndTag', 'VExpressionContainer', 'VFilter', 'VFilterSequenceExpression', 'VForExpression', 'VIdentifier', 'VLiteral', 'VOnExpression', 'VSlotScopeExpression', 'VStartTag', 'VText']) | ||
const KNOWN_NODES = new Set([ | ||
'ArrayExpression', | ||
'ArrayPattern', | ||
'ArrowFunctionExpression', | ||
'AssignmentExpression', | ||
'AssignmentPattern', | ||
'AwaitExpression', | ||
'BinaryExpression', | ||
'BlockStatement', | ||
'BreakStatement', | ||
'CallExpression', | ||
'CatchClause', | ||
'ClassBody', | ||
'ClassDeclaration', | ||
'ClassExpression', | ||
'ConditionalExpression', | ||
'ContinueStatement', | ||
'DebuggerStatement', | ||
'DoWhileStatement', | ||
'EmptyStatement', | ||
'ExperimentalRestProperty', | ||
'ExperimentalSpreadProperty', | ||
'ExportAllDeclaration', | ||
'ExportDefaultDeclaration', | ||
'ExportNamedDeclaration', | ||
'ExportSpecifier', | ||
'ExpressionStatement', | ||
'ForInStatement', | ||
'ForOfStatement', | ||
'ForStatement', | ||
'FunctionDeclaration', | ||
'FunctionExpression', | ||
'Identifier', | ||
'IfStatement', | ||
'ImportDeclaration', | ||
'ImportDefaultSpecifier', | ||
'ImportNamespaceSpecifier', | ||
'ImportSpecifier', | ||
'LabeledStatement', | ||
'Literal', | ||
'LogicalExpression', | ||
'MemberExpression', | ||
'MetaProperty', | ||
'MethodDefinition', | ||
'NewExpression', | ||
'ObjectExpression', | ||
'ObjectPattern', | ||
'Program', | ||
'Property', | ||
'RestElement', | ||
'ReturnStatement', | ||
'SequenceExpression', | ||
'SpreadElement', | ||
'Super', | ||
'SwitchCase', | ||
'SwitchStatement', | ||
'TaggedTemplateExpression', | ||
'TemplateElement', | ||
'TemplateLiteral', | ||
'ThisExpression', | ||
'ThrowStatement', | ||
'TryStatement', | ||
'UnaryExpression', | ||
'UpdateExpression', | ||
'VariableDeclaration', | ||
'VariableDeclarator', | ||
'WhileStatement', | ||
'WithStatement', | ||
'YieldExpression', | ||
'VAttribute', | ||
'VDirectiveKey', | ||
'VDocumentFragment', | ||
'VElement', | ||
'VEndTag', | ||
'VExpressionContainer', | ||
'VFilter', | ||
'VFilterSequenceExpression', | ||
'VForExpression', | ||
'VIdentifier', | ||
'VLiteral', | ||
'VOnExpression', | ||
'VSlotScopeExpression', | ||
'VStartTag', | ||
'VText' | ||
]) | ||
const LT_CHAR = /[\r\n\u2028\u2029]/ | ||
const LINES = /[^\r\n\u2028\u2029]+(?:$|\r\n|[\r\n\u2028\u2029])/g | ||
const BLOCK_COMMENT_PREFIX = /^\s*\*/ | ||
const ITERATION_OPTS = Object.freeze({ includeComments: true, filter: isNotWhitespace }) | ||
const ITERATION_OPTS = Object.freeze({ | ||
includeComments: true, | ||
filter: isNotWhitespace | ||
}) | ||
const PREFORMATTED_ELEMENT_NAMES = ['pre', 'textarea'] | ||
@@ -46,17 +133,20 @@ | ||
*/ | ||
function parseOptions (type, options, defaultOptions) { | ||
const ret = Object.assign({ | ||
indentChar: ' ', | ||
indentSize: 2, | ||
baseIndent: 0, | ||
attribute: 1, | ||
closeBracket: { | ||
startTag: 0, | ||
endTag: 0, | ||
selfClosingTag: 0 | ||
function parseOptions(type, options, defaultOptions) { | ||
const ret = Object.assign( | ||
{ | ||
indentChar: ' ', | ||
indentSize: 2, | ||
baseIndent: 0, | ||
attribute: 1, | ||
closeBracket: { | ||
startTag: 0, | ||
endTag: 0, | ||
selfClosingTag: 0 | ||
}, | ||
switchCase: 0, | ||
alignAttributesVertically: true, | ||
ignores: [] | ||
}, | ||
switchCase: 0, | ||
alignAttributesVertically: true, | ||
ignores: [] | ||
}, defaultOptions) | ||
defaultOptions | ||
) | ||
@@ -112,3 +202,3 @@ if (Number.isSafeInteger(type)) { | ||
*/ | ||
function isArrow (token) { | ||
function isArrow(token) { | ||
return token != null && token.type === 'Punctuator' && token.value === '=>' | ||
@@ -122,3 +212,3 @@ } | ||
*/ | ||
function isLeftParen (token) { | ||
function isLeftParen(token) { | ||
return token != null && token.type === 'Punctuator' && token.value === '(' | ||
@@ -132,3 +222,3 @@ } | ||
*/ | ||
function isNotLeftParen (token) { | ||
function isNotLeftParen(token) { | ||
return token != null && (token.type !== 'Punctuator' || token.value !== '(') | ||
@@ -142,3 +232,3 @@ } | ||
*/ | ||
function isRightParen (token) { | ||
function isRightParen(token) { | ||
return token != null && token.type === 'Punctuator' && token.value === ')' | ||
@@ -152,3 +242,3 @@ } | ||
*/ | ||
function isNotRightParen (token) { | ||
function isNotRightParen(token) { | ||
return token != null && (token.type !== 'Punctuator' || token.value !== ')') | ||
@@ -162,3 +252,3 @@ } | ||
*/ | ||
function isLeftBrace (token) { | ||
function isLeftBrace(token) { | ||
return token != null && token.type === 'Punctuator' && token.value === '{' | ||
@@ -172,3 +262,3 @@ } | ||
*/ | ||
function isRightBrace (token) { | ||
function isRightBrace(token) { | ||
return token != null && token.type === 'Punctuator' && token.value === '}' | ||
@@ -182,3 +272,3 @@ } | ||
*/ | ||
function isLeftBracket (token) { | ||
function isLeftBracket(token) { | ||
return token != null && token.type === 'Punctuator' && token.value === '[' | ||
@@ -192,3 +282,3 @@ } | ||
*/ | ||
function isRightBracket (token) { | ||
function isRightBracket(token) { | ||
return token != null && token.type === 'Punctuator' && token.value === ']' | ||
@@ -202,3 +292,3 @@ } | ||
*/ | ||
function isSemicolon (token) { | ||
function isSemicolon(token) { | ||
return token != null && token.type === 'Punctuator' && token.value === ';' | ||
@@ -212,3 +302,3 @@ } | ||
*/ | ||
function isComma (token) { | ||
function isComma(token) { | ||
return token != null && token.type === 'Punctuator' && token.value === ',' | ||
@@ -222,3 +312,3 @@ } | ||
*/ | ||
function isNotWhitespace (token) { | ||
function isNotWhitespace(token) { | ||
return token != null && token.type !== 'HTMLWhitespace' | ||
@@ -232,4 +322,10 @@ } | ||
*/ | ||
function isComment (token) { | ||
return token != null && (token.type === 'Block' || token.type === 'Line' || token.type === 'Shebang' || token.type.endsWith('Comment')) | ||
function isComment(token) { | ||
return ( | ||
token != null && | ||
(token.type === 'Block' || | ||
token.type === 'Line' || | ||
token.type === 'Shebang' || | ||
token.type.endsWith('Comment')) | ||
) | ||
} | ||
@@ -242,4 +338,10 @@ | ||
*/ | ||
function isNotComment (token) { | ||
return token != null && token.type !== 'Block' && token.type !== 'Line' && token.type !== 'Shebang' && !token.type.endsWith('Comment') | ||
function isNotComment(token) { | ||
return ( | ||
token != null && | ||
token.type !== 'Block' && | ||
token.type !== 'Line' && | ||
token.type !== 'Shebang' && | ||
!token.type.endsWith('Comment') | ||
) | ||
} | ||
@@ -252,3 +354,3 @@ | ||
*/ | ||
function isNotEmptyTextNode (node) { | ||
function isNotEmptyTextNode(node) { | ||
return !(node.type === 'VText' && node.value.trim() === '') | ||
@@ -262,3 +364,3 @@ } | ||
*/ | ||
function isPipeOperator (token) { | ||
function isPipeOperator(token) { | ||
return token != null && token.type === 'Punctuator' && token.value === '|' | ||
@@ -272,3 +374,3 @@ } | ||
*/ | ||
function last (xs) { | ||
function last(xs) { | ||
return xs.length === 0 ? undefined : xs[xs.length - 1] | ||
@@ -284,3 +386,3 @@ } | ||
*/ | ||
function isBeginningOfLine (node, index, nodes) { | ||
function isBeginningOfLine(node, index, nodes) { | ||
if (node != null) { | ||
@@ -304,14 +406,9 @@ for (let i = index - 1; i >= 0; --i) { | ||
*/ | ||
function isClosingToken (token) { | ||
return token != null && ( | ||
token.type === 'HTMLEndTagOpen' || | ||
token.type === 'VExpressionEnd' || | ||
( | ||
token.type === 'Punctuator' && | ||
( | ||
token.value === ')' || | ||
token.value === '}' || | ||
token.value === ']' | ||
) | ||
) | ||
function isClosingToken(token) { | ||
return ( | ||
token != null && | ||
(token.type === 'HTMLEndTagOpen' || | ||
token.type === 'VExpressionEnd' || | ||
(token.type === 'Punctuator' && | ||
(token.value === ')' || token.value === '}' || token.value === ']'))) | ||
) | ||
@@ -328,6 +425,14 @@ } | ||
*/ | ||
module.exports.defineVisitor = function create (context, tokenStore, defaultOptions) { | ||
module.exports.defineVisitor = function create( | ||
context, | ||
tokenStore, | ||
defaultOptions | ||
) { | ||
if (!context.getFilename().endsWith('.vue')) return {} | ||
const options = parseOptions(context.options[0], context.options[1] || {}, defaultOptions) | ||
const options = parseOptions( | ||
context.options[0], | ||
context.options[1] || {}, | ||
defaultOptions | ||
) | ||
const sourceCode = context.getSourceCode() | ||
@@ -345,3 +450,3 @@ const offsets = new Map() | ||
*/ | ||
function setOffset (token, offset, baseToken) { | ||
function setOffset(token, offset, baseToken) { | ||
assert(baseToken != null, "'baseToken' should not be null or undefined.") | ||
@@ -373,3 +478,3 @@ | ||
*/ | ||
function setBaseline (token) { | ||
function setBaseline(token) { | ||
const offsetInfo = offsets.get(token) | ||
@@ -386,16 +491,22 @@ if (offsetInfo != null) { | ||
*/ | ||
function setPreformattedTokens (node) { | ||
const endToken = (node.endTag && tokenStore.getFirstToken(node.endTag)) || tokenStore.getTokenAfter(node) | ||
function setPreformattedTokens(node) { | ||
const endToken = | ||
(node.endTag && tokenStore.getFirstToken(node.endTag)) || | ||
tokenStore.getTokenAfter(node) | ||
const option = { | ||
includeComments: true, | ||
filter: token => token != null && ( | ||
token.type === 'HTMLText' || | ||
token.type === 'HTMLRCDataText' || | ||
token.type === 'HTMLTagOpen' || | ||
token.type === 'HTMLEndTagOpen' || | ||
token.type === 'HTMLComment' | ||
) | ||
filter: (token) => | ||
token != null && | ||
(token.type === 'HTMLText' || | ||
token.type === 'HTMLRCDataText' || | ||
token.type === 'HTMLTagOpen' || | ||
token.type === 'HTMLEndTagOpen' || | ||
token.type === 'HTMLComment') | ||
} | ||
for (const token of tokenStore.getTokensBetween(node.startTag, endToken, option)) { | ||
for (const token of tokenStore.getTokensBetween( | ||
node.startTag, | ||
endToken, | ||
option | ||
)) { | ||
ignoreTokens.add(token) | ||
@@ -413,3 +524,3 @@ } | ||
*/ | ||
function getFirstAndLastTokens (node, borderOffset) { | ||
function getFirstAndLastTokens(node, borderOffset) { | ||
borderOffset |= 0 | ||
@@ -422,3 +533,9 @@ | ||
let t, u | ||
while ((t = tokenStore.getTokenBefore(firstToken)) != null && (u = tokenStore.getTokenAfter(lastToken)) != null && isLeftParen(t) && isRightParen(u) && t.range[0] >= borderOffset) { | ||
while ( | ||
(t = tokenStore.getTokenBefore(firstToken)) != null && | ||
(u = tokenStore.getTokenAfter(lastToken)) != null && | ||
isLeftParen(t) && | ||
isRightParen(u) && | ||
t.range[0] >= borderOffset | ||
) { | ||
firstToken = t | ||
@@ -442,3 +559,3 @@ lastToken = u | ||
*/ | ||
function processNodeList (nodeList, left, right, offset, alignVertically) { | ||
function processNodeList(nodeList, left, right, offset, alignVertically) { | ||
let t | ||
@@ -460,3 +577,6 @@ const leftToken = (left && tokenStore.getFirstToken(left)) || left | ||
} | ||
const elementTokens = getFirstAndLastTokens(node, lastToken != null ? lastToken.range[1] : 0) | ||
const elementTokens = getFirstAndLastTokens( | ||
node, | ||
lastToken != null ? lastToken.range[1] : 0 | ||
) | ||
@@ -540,3 +660,3 @@ // Collect comma/comment tokens between the last token of the previous node and the first token of this node. | ||
*/ | ||
function processMaybeBlock (node, baseToken) { | ||
function processMaybeBlock(node, baseToken) { | ||
const firstToken = getFirstAndLastTokens(node).firstToken | ||
@@ -551,3 +671,3 @@ setOffset(firstToken, isLeftBrace(firstToken) ? 0 : 1, baseToken) | ||
*/ | ||
function getPrefixTokens (node) { | ||
function getPrefixTokens(node) { | ||
const prefixes = [] | ||
@@ -572,3 +692,3 @@ | ||
*/ | ||
function getChainHeadToken (node) { | ||
function getChainHeadToken(node) { | ||
const type = node.type | ||
@@ -599,3 +719,3 @@ while (node.parent.type === type) { | ||
*/ | ||
function isBeginningOfElement (token, belongingNode) { | ||
function isBeginningOfElement(token, belongingNode) { | ||
let node = belongingNode | ||
@@ -621,16 +741,24 @@ | ||
if (t === 'CallExpression' || t === 'NewExpression') { | ||
const openParen = tokenStore.getTokenAfter(parent.callee, isNotRightParen) | ||
return parent.arguments.some(param => | ||
getFirstAndLastTokens(param, openParen.range[1]).firstToken.range[0] === token.range[0] | ||
const openParen = tokenStore.getTokenAfter( | ||
parent.callee, | ||
isNotRightParen | ||
) | ||
return parent.arguments.some( | ||
(param) => | ||
getFirstAndLastTokens(param, openParen.range[1]).firstToken | ||
.range[0] === token.range[0] | ||
) | ||
} | ||
if (t === 'ArrayExpression') { | ||
return parent.elements.some(element => | ||
element != null && | ||
getFirstAndLastTokens(element).firstToken.range[0] === token.range[0] | ||
return parent.elements.some( | ||
(element) => | ||
element != null && | ||
getFirstAndLastTokens(element).firstToken.range[0] === | ||
token.range[0] | ||
) | ||
} | ||
if (t === 'SequenceExpression') { | ||
return parent.expressions.some(expr => | ||
getFirstAndLastTokens(expr).firstToken.range[0] === token.range[0] | ||
return parent.expressions.some( | ||
(expr) => | ||
getFirstAndLastTokens(expr).firstToken.range[0] === token.range[0] | ||
) | ||
@@ -651,3 +779,3 @@ } | ||
*/ | ||
function processTopLevelNode (node, expectedIndent) { | ||
function processTopLevelNode(node, expectedIndent) { | ||
const token = tokenStore.getFirstToken(node) | ||
@@ -658,3 +786,8 @@ const offsetInfo = offsets.get(token) | ||
} else { | ||
offsets.set(token, { baseToken: null, offset: 0, baseline: false, expectedIndent }) | ||
offsets.set(token, { | ||
baseToken: null, | ||
offset: 0, | ||
baseline: false, | ||
expectedIndent | ||
}) | ||
} | ||
@@ -668,3 +801,3 @@ } | ||
*/ | ||
function ignore (node) { | ||
function ignore(node) { | ||
for (const token of tokenStore.getTokens(node)) { | ||
@@ -681,3 +814,3 @@ offsets.delete(token) | ||
*/ | ||
function processIgnores (visitor) { | ||
function processIgnores(visitor) { | ||
for (const ignorePattern of options.ignores) { | ||
@@ -706,3 +839,3 @@ const key = `${ignorePattern}:exit` | ||
*/ | ||
function getExpectedIndents (tokens) { | ||
function getExpectedIndents(tokens) { | ||
const expectedIndents = [] | ||
@@ -719,4 +852,11 @@ | ||
const baseOffsetInfo = offsets.get(offsetInfo.baseToken) | ||
if (baseOffsetInfo != null && baseOffsetInfo.expectedIndent != null && (i === 0 || !baseOffsetInfo.baseline)) { | ||
expectedIndents.push(baseOffsetInfo.expectedIndent + (offsetInfo.offset * options.indentSize)) | ||
if ( | ||
baseOffsetInfo != null && | ||
baseOffsetInfo.expectedIndent != null && | ||
(i === 0 || !baseOffsetInfo.baseline) | ||
) { | ||
expectedIndents.push( | ||
baseOffsetInfo.expectedIndent + | ||
offsetInfo.offset * options.indentSize | ||
) | ||
if (baseOffsetInfo.baseline) { | ||
@@ -744,3 +884,3 @@ break | ||
*/ | ||
function getIndentText (firstToken) { | ||
function getIndentText(firstToken) { | ||
const text = sourceCode.text | ||
@@ -763,3 +903,3 @@ let i = firstToken.range[0] - 1 | ||
*/ | ||
function defineFix (token, actualIndent, expectedIndent) { | ||
function defineFix(token, actualIndent, expectedIndent) { | ||
if (token.type === 'Block' && token.loc.start.line !== token.loc.end.line) { | ||
@@ -769,4 +909,4 @@ // Fix indentation in multiline block comments. | ||
const firstLine = lines.shift() | ||
if (lines.every(l => BLOCK_COMMENT_PREFIX.test(l))) { | ||
return fixer => { | ||
if (lines.every((l) => BLOCK_COMMENT_PREFIX.test(l))) { | ||
return (fixer) => { | ||
const range = [token.range[0] - actualIndent, token.range[1]] | ||
@@ -777,3 +917,5 @@ const indent = options.indentChar.repeat(expectedIndent) | ||
range, | ||
`${indent}${firstLine}${lines.map(l => l.replace(BLOCK_COMMENT_PREFIX, `${indent} *`)).join('')}` | ||
`${indent}${firstLine}${lines | ||
.map((l) => l.replace(BLOCK_COMMENT_PREFIX, `${indent} *`)) | ||
.join('')}` | ||
) | ||
@@ -784,3 +926,3 @@ } | ||
return fixer => { | ||
return (fixer) => { | ||
const range = [token.range[0] - actualIndent, token.range[0]] | ||
@@ -799,3 +941,3 @@ const indent = options.indentChar.repeat(expectedIndent) | ||
*/ | ||
function validateCore (token, expectedIndent, optionalExpectedIndents) { | ||
function validateCore(token, expectedIndent, optionalExpectedIndents) { | ||
const line = token.loc.start.line | ||
@@ -812,3 +954,3 @@ const indentText = getIndentText(token) | ||
const actualIndent = token.loc.start.column | ||
const unit = (options.indentChar === '\t' ? 'tab' : 'space') | ||
const unit = options.indentChar === '\t' ? 'tab' : 'space' | ||
@@ -822,3 +964,4 @@ for (let i = 0; i < indentText.length; ++i) { | ||
}, | ||
message: 'Expected {{expected}} character, but found {{actual}} character.', | ||
message: | ||
'Expected {{expected}} character, but found {{actual}} character.', | ||
data: { | ||
@@ -834,3 +977,7 @@ expected: JSON.stringify(options.indentChar), | ||
if (actualIndent !== expectedIndent && (optionalExpectedIndents == null || !optionalExpectedIndents.includes(actualIndent))) { | ||
if ( | ||
actualIndent !== expectedIndent && | ||
(optionalExpectedIndents == null || | ||
!optionalExpectedIndents.includes(actualIndent)) | ||
) { | ||
context.report({ | ||
@@ -841,3 +988,4 @@ loc: { | ||
}, | ||
message: 'Expected indentation of {{expectedIndent}} {{unit}}{{expectedIndentPlural}} but found {{actualIndent}} {{unit}}{{actualIndentPlural}}.', | ||
message: | ||
'Expected indentation of {{expectedIndent}} {{unit}}{{expectedIndentPlural}} but found {{actualIndent}} {{unit}}{{actualIndentPlural}}.', | ||
data: { | ||
@@ -847,4 +995,4 @@ expectedIndent, | ||
unit, | ||
expectedIndentPlural: (expectedIndent === 1) ? '' : 's', | ||
actualIndentPlural: (actualIndent === 1) ? '' : 's' | ||
expectedIndentPlural: expectedIndent === 1 ? '' : 's', | ||
actualIndentPlural: actualIndent === 1 ? '' : 's' | ||
}, | ||
@@ -863,3 +1011,7 @@ fix: defineFix(token, actualIndent, expectedIndent) | ||
*/ | ||
function getCommentExpectedIndents (nextToken, nextExpectedIndent, lastExpectedIndent) { | ||
function getCommentExpectedIndents( | ||
nextToken, | ||
nextExpectedIndent, | ||
lastExpectedIndent | ||
) { | ||
if (typeof lastExpectedIndent === 'number' && isClosingToken(nextToken)) { | ||
@@ -897,3 +1049,3 @@ if (nextExpectedIndent === lastExpectedIndent) { | ||
*/ | ||
function validate (tokens, comments, lastToken) { | ||
function validate(tokens, comments, lastToken) { | ||
// Calculate and save expected indentation. | ||
@@ -932,7 +1084,11 @@ const firstToken = tokens[0] | ||
if (options.indentChar === ' ') { | ||
offsetInfo.expectedIndent = Math.max(0, token.loc.start.column + expectedBaseIndent - actualIndent) | ||
offsetInfo.expectedIndent = Math.max( | ||
0, | ||
token.loc.start.column + expectedBaseIndent - actualIndent | ||
) | ||
} else { | ||
// In hard-tabs mode, it cannot align tokens strictly, so use one additional offset. | ||
// But the additional offset isn't needed if it's at the beginning of the line. | ||
offsetInfo.expectedIndent = expectedBaseIndent + (token === tokens[0] ? 0 : 1) | ||
offsetInfo.expectedIndent = | ||
expectedBaseIndent + (token === tokens[0] ? 0 : 1) | ||
} | ||
@@ -942,3 +1098,5 @@ baseline.add(token) | ||
// The base token is a baseline token on this line, so inherit it. | ||
offsetInfo.expectedIndent = offsets.get(offsetInfo.baseToken).expectedIndent | ||
offsetInfo.expectedIndent = offsets.get( | ||
offsetInfo.baseToken | ||
).expectedIndent | ||
baseline.add(token) | ||
@@ -961,3 +1119,7 @@ } else { | ||
const lastExpectedIndent = lastOffsetInfo && lastOffsetInfo.expectedIndent | ||
const commentOptionalExpectedIndents = getCommentExpectedIndents(firstToken, expectedIndent, lastExpectedIndent) | ||
const commentOptionalExpectedIndents = getCommentExpectedIndents( | ||
firstToken, | ||
expectedIndent, | ||
lastExpectedIndent | ||
) | ||
@@ -967,8 +1129,11 @@ // Validate. | ||
const commentExpectedIndents = getExpectedIndents([comment]) | ||
const commentExpectedIndent = | ||
commentExpectedIndents | ||
? commentExpectedIndents.expectedIndent | ||
: commentOptionalExpectedIndents[0] | ||
const commentExpectedIndent = commentExpectedIndents | ||
? commentExpectedIndents.expectedIndent | ||
: commentOptionalExpectedIndents[0] | ||
validateCore(comment, commentExpectedIndent, commentOptionalExpectedIndents) | ||
validateCore( | ||
comment, | ||
commentExpectedIndent, | ||
commentOptionalExpectedIndents | ||
) | ||
} | ||
@@ -983,3 +1148,3 @@ validateCore(firstToken, expectedIndent) | ||
return processIgnores({ | ||
VAttribute (node) { | ||
VAttribute(node) { | ||
const keyToken = tokenStore.getFirstToken(node) | ||
@@ -998,7 +1163,13 @@ const eqToken = tokenStore.getTokenAfter(node.key) | ||
VElement (node) { | ||
VElement(node) { | ||
if (!PREFORMATTED_ELEMENT_NAMES.includes(node.name)) { | ||
const isTopLevel = node.parent.type !== 'VElement' | ||
const offset = isTopLevel ? options.baseIndent : 1 | ||
processNodeList(node.children.filter(isNotEmptyTextNode), node.startTag, node.endTag, offset, false) | ||
processNodeList( | ||
node.children.filter(isNotEmptyTextNode), | ||
node.startTag, | ||
node.endTag, | ||
offset, | ||
false | ||
) | ||
} else { | ||
@@ -1012,3 +1183,3 @@ const startTagToken = tokenStore.getFirstToken(node) | ||
VEndTag (node) { | ||
VEndTag(node) { | ||
const element = node.parent | ||
@@ -1023,4 +1194,7 @@ const startTagOpenToken = tokenStore.getFirstToken(element.startTag) | ||
VExpressionContainer (node) { | ||
if (node.expression != null && node.range[0] !== node.expression.range[0]) { | ||
VExpressionContainer(node) { | ||
if ( | ||
node.expression != null && | ||
node.range[0] !== node.expression.range[0] | ||
) { | ||
const startQuoteToken = tokenStore.getFirstToken(node) | ||
@@ -1035,3 +1209,3 @@ const endQuoteToken = tokenStore.getLastToken(node) | ||
VFilter (node) { | ||
VFilter(node) { | ||
const idToken = tokenStore.getFirstToken(node) | ||
@@ -1046,3 +1220,3 @@ const lastToken = tokenStore.getLastToken(node) | ||
VFilterSequenceExpression (node) { | ||
VFilterSequenceExpression(node) { | ||
if (node.filters.length === 0) { | ||
@@ -1065,3 +1239,3 @@ return | ||
VForExpression (node) { | ||
VForExpression(node) { | ||
const firstToken = tokenStore.getFirstToken(node) | ||
@@ -1080,7 +1254,7 @@ const lastOfLeft = last(node.left) || firstToken | ||
VOnExpression (node) { | ||
VOnExpression(node) { | ||
processNodeList(node.body, null, null, 0) | ||
}, | ||
VStartTag (node) { | ||
VStartTag(node) { | ||
const openToken = tokenStore.getFirstToken(node) | ||
@@ -1097,5 +1271,6 @@ const closeToken = tokenStore.getLastToken(node) | ||
if (closeToken != null && closeToken.type.endsWith('TagClose')) { | ||
const offset = closeToken.type !== 'HTMLSelfClosingTagClose' | ||
? options.closeBracket.startTag | ||
: options.closeBracket.selfClosingTag | ||
const offset = | ||
closeToken.type !== 'HTMLSelfClosingTagClose' | ||
? options.closeBracket.startTag | ||
: options.closeBracket.selfClosingTag | ||
setOffset(closeToken, offset, openToken) | ||
@@ -1105,3 +1280,3 @@ } | ||
VText (node) { | ||
VText(node) { | ||
const tokens = tokenStore.getTokens(node, isNotWhitespace) | ||
@@ -1115,7 +1290,12 @@ const firstTokenInfo = offsets.get(tokenStore.getFirstToken(node)) | ||
'ArrayExpression, ArrayPattern' (node) { | ||
processNodeList(node.elements, tokenStore.getFirstToken(node), tokenStore.getLastToken(node), 1) | ||
'ArrayExpression, ArrayPattern'(node) { | ||
processNodeList( | ||
node.elements, | ||
tokenStore.getFirstToken(node), | ||
tokenStore.getLastToken(node), | ||
1 | ||
) | ||
}, | ||
ArrowFunctionExpression (node) { | ||
ArrowFunctionExpression(node) { | ||
const firstToken = tokenStore.getFirstToken(node) | ||
@@ -1130,3 +1310,6 @@ const secondToken = tokenStore.getTokenAfter(firstToken) | ||
if (isLeftParen(leftToken)) { | ||
const rightToken = tokenStore.getTokenAfter(last(node.params) || leftToken, isRightParen) | ||
const rightToken = tokenStore.getTokenAfter( | ||
last(node.params) || leftToken, | ||
isRightParen | ||
) | ||
processNodeList(node.params, leftToken, rightToken, 1) | ||
@@ -1139,3 +1322,5 @@ } | ||
'AssignmentExpression, AssignmentPattern, BinaryExpression, LogicalExpression' (node) { | ||
'AssignmentExpression, AssignmentPattern, BinaryExpression, LogicalExpression'( | ||
node | ||
) { | ||
const leftToken = getChainHeadToken(node) | ||
@@ -1145,7 +1330,6 @@ const opToken = tokenStore.getTokenAfter(node.left, isNotRightParen) | ||
const prevToken = tokenStore.getTokenBefore(leftToken) | ||
const shouldIndent = ( | ||
const shouldIndent = | ||
prevToken == null || | ||
prevToken.loc.end.line === leftToken.loc.start.line || | ||
isBeginningOfElement(leftToken, node) | ||
) | ||
@@ -1155,3 +1339,3 @@ setOffset([opToken, rightToken], shouldIndent ? 1 : 0, leftToken) | ||
'AwaitExpression, RestElement, SpreadElement, UnaryExpression' (node) { | ||
'AwaitExpression, RestElement, SpreadElement, UnaryExpression'(node) { | ||
const firstToken = tokenStore.getFirstToken(node) | ||
@@ -1163,7 +1347,12 @@ const nextToken = tokenStore.getTokenAfter(firstToken) | ||
'BlockStatement, ClassBody' (node) { | ||
processNodeList(node.body, tokenStore.getFirstToken(node), tokenStore.getLastToken(node), 1) | ||
'BlockStatement, ClassBody'(node) { | ||
processNodeList( | ||
node.body, | ||
tokenStore.getFirstToken(node), | ||
tokenStore.getLastToken(node), | ||
1 | ||
) | ||
}, | ||
'BreakStatement, ContinueStatement, ReturnStatement, ThrowStatement' (node) { | ||
'BreakStatement, ContinueStatement, ReturnStatement, ThrowStatement'(node) { | ||
if (node.argument != null || node.label != null) { | ||
@@ -1177,3 +1366,3 @@ const firstToken = tokenStore.getFirstToken(node) | ||
CallExpression (node) { | ||
CallExpression(node) { | ||
const firstToken = tokenStore.getFirstToken(node) | ||
@@ -1187,3 +1376,3 @@ const rightToken = tokenStore.getLastToken(node) | ||
CatchClause (node) { | ||
CatchClause(node) { | ||
const firstToken = tokenStore.getFirstToken(node) | ||
@@ -1202,3 +1391,3 @@ const bodyToken = tokenStore.getFirstToken(node.body) | ||
'ClassDeclaration, ClassExpression' (node) { | ||
'ClassDeclaration, ClassExpression'(node) { | ||
const firstToken = tokenStore.getFirstToken(node) | ||
@@ -1219,3 +1408,3 @@ const bodyToken = tokenStore.getFirstToken(node.body) | ||
ConditionalExpression (node) { | ||
ConditionalExpression(node) { | ||
const prevToken = tokenStore.getTokenBefore(node) | ||
@@ -1225,3 +1414,6 @@ const firstToken = tokenStore.getFirstToken(node) | ||
const consequentToken = tokenStore.getTokenAfter(questionToken) | ||
const colonToken = tokenStore.getTokenAfter(node.consequent, isNotRightParen) | ||
const colonToken = tokenStore.getTokenAfter( | ||
node.consequent, | ||
isNotRightParen | ||
) | ||
const alternateToken = tokenStore.getTokenAfter(colonToken) | ||
@@ -1234,3 +1426,7 @@ const isFlat = | ||
if (isFlat) { | ||
setOffset([questionToken, consequentToken, colonToken, alternateToken], 0, firstToken) | ||
setOffset( | ||
[questionToken, consequentToken, colonToken, alternateToken], | ||
0, | ||
firstToken | ||
) | ||
} else { | ||
@@ -1242,3 +1438,3 @@ setOffset([questionToken, colonToken], 1, firstToken) | ||
DoWhileStatement (node) { | ||
DoWhileStatement(node) { | ||
const doToken = tokenStore.getFirstToken(node) | ||
@@ -1249,3 +1445,5 @@ const whileToken = tokenStore.getTokenAfter(node.body, isNotRightParen) | ||
const lastToken = tokenStore.getLastToken(node) | ||
const rightToken = isSemicolon(lastToken) ? tokenStore.getTokenBefore(lastToken) : lastToken | ||
const rightToken = isSemicolon(lastToken) | ||
? tokenStore.getTokenBefore(lastToken) | ||
: lastToken | ||
@@ -1259,3 +1457,3 @@ processMaybeBlock(node.body, doToken) | ||
ExportAllDeclaration (node) { | ||
ExportAllDeclaration(node) { | ||
const tokens = tokenStore.getTokens(node) | ||
@@ -1269,10 +1467,11 @@ const firstToken = tokens.shift() | ||
ExportDefaultDeclaration (node) { | ||
ExportDefaultDeclaration(node) { | ||
const exportToken = tokenStore.getFirstToken(node) | ||
const defaultToken = tokenStore.getFirstToken(node, 1) | ||
const declarationToken = getFirstAndLastTokens(node.declaration).firstToken | ||
const declarationToken = getFirstAndLastTokens(node.declaration) | ||
.firstToken | ||
setOffset([defaultToken, declarationToken], 1, exportToken) | ||
}, | ||
ExportNamedDeclaration (node) { | ||
ExportNamedDeclaration(node) { | ||
const exportToken = tokenStore.getFirstToken(node) | ||
@@ -1291,3 +1490,6 @@ if (node.declaration) { | ||
const maybeFromToken = tokenStore.getTokenAfter(rightParenToken) | ||
if (maybeFromToken != null && sourceCode.getText(maybeFromToken) === 'from') { | ||
if ( | ||
maybeFromToken != null && | ||
sourceCode.getText(maybeFromToken) === 'from' | ||
) { | ||
const fromToken = maybeFromToken | ||
@@ -1300,3 +1502,3 @@ const nameToken = tokenStore.getTokenAfter(fromToken) | ||
ExportSpecifier (node) { | ||
ExportSpecifier(node) { | ||
const tokens = tokenStore.getTokens(node) | ||
@@ -1307,3 +1509,3 @@ const firstToken = tokens.shift() | ||
'ForInStatement, ForOfStatement' (node) { | ||
'ForInStatement, ForOfStatement'(node) { | ||
const forToken = tokenStore.getFirstToken(node) | ||
@@ -1314,3 +1516,6 @@ const leftParenToken = tokenStore.getTokenAfter(forToken) | ||
const rightToken = tokenStore.getTokenAfter(inToken) | ||
const rightParenToken = tokenStore.getTokenBefore(node.body, isNotLeftParen) | ||
const rightParenToken = tokenStore.getTokenBefore( | ||
node.body, | ||
isNotLeftParen | ||
) | ||
@@ -1325,13 +1530,21 @@ setOffset(leftParenToken, 1, forToken) | ||
ForStatement (node) { | ||
ForStatement(node) { | ||
const forToken = tokenStore.getFirstToken(node) | ||
const leftParenToken = tokenStore.getTokenAfter(forToken) | ||
const rightParenToken = tokenStore.getTokenBefore(node.body, isNotLeftParen) | ||
const rightParenToken = tokenStore.getTokenBefore( | ||
node.body, | ||
isNotLeftParen | ||
) | ||
setOffset(leftParenToken, 1, forToken) | ||
processNodeList([node.init, node.test, node.update], leftParenToken, rightParenToken, 1) | ||
processNodeList( | ||
[node.init, node.test, node.update], | ||
leftParenToken, | ||
rightParenToken, | ||
1 | ||
) | ||
processMaybeBlock(node.body, forToken) | ||
}, | ||
'FunctionDeclaration, FunctionExpression' (node) { | ||
'FunctionDeclaration, FunctionExpression'(node) { | ||
const firstToken = tokenStore.getFirstToken(node) | ||
@@ -1341,3 +1554,6 @@ if (isLeftParen(firstToken)) { | ||
const leftToken = firstToken | ||
const rightToken = tokenStore.getTokenAfter(last(node.params) || leftToken, isRightParen) | ||
const rightToken = tokenStore.getTokenAfter( | ||
last(node.params) || leftToken, | ||
isRightParen | ||
) | ||
const bodyToken = tokenStore.getFirstToken(node.body) | ||
@@ -1349,7 +1565,16 @@ | ||
// Normal functions. | ||
const functionToken = node.async ? tokenStore.getTokenAfter(firstToken) : firstToken | ||
const starToken = node.generator ? tokenStore.getTokenAfter(functionToken) : null | ||
const functionToken = node.async | ||
? tokenStore.getTokenAfter(firstToken) | ||
: firstToken | ||
const starToken = node.generator | ||
? tokenStore.getTokenAfter(functionToken) | ||
: null | ||
const idToken = node.id && tokenStore.getFirstToken(node.id) | ||
const leftToken = tokenStore.getTokenAfter(idToken || starToken || functionToken) | ||
const rightToken = tokenStore.getTokenAfter(last(node.params) || leftToken, isRightParen) | ||
const leftToken = tokenStore.getTokenAfter( | ||
idToken || starToken || functionToken | ||
) | ||
const rightToken = tokenStore.getTokenAfter( | ||
last(node.params) || leftToken, | ||
isRightParen | ||
) | ||
const bodyToken = tokenStore.getFirstToken(node.body) | ||
@@ -1372,6 +1597,9 @@ | ||
IfStatement (node) { | ||
IfStatement(node) { | ||
const ifToken = tokenStore.getFirstToken(node) | ||
const ifLeftParenToken = tokenStore.getTokenAfter(ifToken) | ||
const ifRightParenToken = tokenStore.getTokenBefore(node.consequent, isRightParen) | ||
const ifRightParenToken = tokenStore.getTokenBefore( | ||
node.consequent, | ||
isRightParen | ||
) | ||
@@ -1383,3 +1611,6 @@ setOffset(ifLeftParenToken, 1, ifToken) | ||
if (node.alternate != null) { | ||
const elseToken = tokenStore.getTokenAfter(node.consequent, isNotRightParen) | ||
const elseToken = tokenStore.getTokenAfter( | ||
node.consequent, | ||
isNotRightParen | ||
) | ||
@@ -1391,3 +1622,3 @@ setOffset(elseToken, 0, ifToken) | ||
ImportDeclaration (node) { | ||
ImportDeclaration(node) { | ||
const firstSpecifier = node.specifiers[0] | ||
@@ -1418,3 +1649,6 @@ const secondSpecifier = node.specifiers[1] | ||
} else if (firstSpecifier.type === 'ImportDefaultSpecifier') { | ||
if (secondSpecifier && secondSpecifier.type === 'ImportNamespaceSpecifier') { | ||
if ( | ||
secondSpecifier && | ||
secondSpecifier.type === 'ImportNamespaceSpecifier' | ||
) { | ||
// There is a pattern: | ||
@@ -1478,3 +1712,3 @@ // import Foo, * as foo from "foo" | ||
ImportSpecifier (node) { | ||
ImportSpecifier(node) { | ||
if (node.local.range[0] !== node.imported.range[0]) { | ||
@@ -1487,3 +1721,3 @@ const tokens = tokenStore.getTokens(node) | ||
ImportNamespaceSpecifier (node) { | ||
ImportNamespaceSpecifier(node) { | ||
const tokens = tokenStore.getTokens(node) | ||
@@ -1494,3 +1728,3 @@ const firstToken = tokens.shift() | ||
LabeledStatement (node) { | ||
LabeledStatement(node) { | ||
const labelToken = tokenStore.getFirstToken(node) | ||
@@ -1503,8 +1737,14 @@ const colonToken = tokenStore.getTokenAfter(labelToken) | ||
'MemberExpression, MetaProperty' (node) { | ||
'MemberExpression, MetaProperty'(node) { | ||
const objectToken = tokenStore.getFirstToken(node) | ||
if (node.computed) { | ||
const leftBracketToken = tokenStore.getTokenBefore(node.property, isLeftBracket) | ||
const leftBracketToken = tokenStore.getTokenBefore( | ||
node.property, | ||
isLeftBracket | ||
) | ||
const propertyToken = tokenStore.getTokenAfter(leftBracketToken) | ||
const rightBracketToken = tokenStore.getTokenAfter(node.property, isRightBracket) | ||
const rightBracketToken = tokenStore.getTokenAfter( | ||
node.property, | ||
isRightBracket | ||
) | ||
@@ -1522,4 +1762,4 @@ setOffset(leftBracketToken, 1, objectToken) | ||
'MethodDefinition, Property' (node) { | ||
const isMethod = (node.type === 'MethodDefinition' || node.method === true) | ||
'MethodDefinition, Property'(node) { | ||
const isMethod = node.type === 'MethodDefinition' || node.method === true | ||
const prefixTokens = getPrefixTokens(node) | ||
@@ -1536,3 +1776,6 @@ const hasPrefix = prefixTokens.length >= 1 | ||
const keyToken = tokenStore.getTokenAfter(keyLeftToken) | ||
const keyRightToken = lastKeyToken = tokenStore.getTokenAfter(node.key, isRightBracket) | ||
const keyRightToken = (lastKeyToken = tokenStore.getTokenAfter( | ||
node.key, | ||
isRightBracket | ||
)) | ||
@@ -1545,3 +1788,3 @@ if (hasPrefix) { | ||
} else { | ||
const idToken = lastKeyToken = tokenStore.getFirstToken(node.key) | ||
const idToken = (lastKeyToken = tokenStore.getFirstToken(node.key)) | ||
@@ -1565,3 +1808,3 @@ if (hasPrefix) { | ||
NewExpression (node) { | ||
NewExpression(node) { | ||
const newToken = tokenStore.getFirstToken(node) | ||
@@ -1581,11 +1824,16 @@ const calleeToken = tokenStore.getTokenAfter(newToken) | ||
'ObjectExpression, ObjectPattern' (node) { | ||
processNodeList(node.properties, tokenStore.getFirstToken(node), tokenStore.getLastToken(node), 1) | ||
'ObjectExpression, ObjectPattern'(node) { | ||
processNodeList( | ||
node.properties, | ||
tokenStore.getFirstToken(node), | ||
tokenStore.getLastToken(node), | ||
1 | ||
) | ||
}, | ||
SequenceExpression (node) { | ||
SequenceExpression(node) { | ||
processNodeList(node.expressions, null, null, 0) | ||
}, | ||
SwitchCase (node) { | ||
SwitchCase(node) { | ||
const caseToken = tokenStore.getFirstToken(node) | ||
@@ -1604,3 +1852,6 @@ | ||
if (node.consequent.length === 1 && node.consequent[0].type === 'BlockStatement') { | ||
if ( | ||
node.consequent.length === 1 && | ||
node.consequent[0].type === 'BlockStatement' | ||
) { | ||
setOffset(tokenStore.getFirstToken(node.consequent[0]), 0, caseToken) | ||
@@ -1613,7 +1864,10 @@ } else if (node.consequent.length >= 1) { | ||
SwitchStatement (node) { | ||
SwitchStatement(node) { | ||
const switchToken = tokenStore.getFirstToken(node) | ||
const leftParenToken = tokenStore.getTokenAfter(switchToken) | ||
const discriminantToken = tokenStore.getTokenAfter(leftParenToken) | ||
const leftBraceToken = tokenStore.getTokenAfter(node.discriminant, isLeftBrace) | ||
const leftBraceToken = tokenStore.getTokenAfter( | ||
node.discriminant, | ||
isLeftBrace | ||
) | ||
const rightParenToken = tokenStore.getTokenBefore(leftBraceToken) | ||
@@ -1626,6 +1880,11 @@ const rightBraceToken = tokenStore.getLastToken(node) | ||
setOffset(leftBraceToken, 0, switchToken) | ||
processNodeList(node.cases, leftBraceToken, rightBraceToken, options.switchCase) | ||
processNodeList( | ||
node.cases, | ||
leftBraceToken, | ||
rightBraceToken, | ||
options.switchCase | ||
) | ||
}, | ||
TaggedTemplateExpression (node) { | ||
TaggedTemplateExpression(node) { | ||
const tagTokens = getFirstAndLastTokens(node.tag, node.range[0]) | ||
@@ -1637,6 +1896,10 @@ const quasiToken = tokenStore.getTokenAfter(tagTokens.lastToken) | ||
TemplateLiteral (node) { | ||
TemplateLiteral(node) { | ||
const firstToken = tokenStore.getFirstToken(node) | ||
const quasiTokens = node.quasis.slice(1).map(n => tokenStore.getFirstToken(n)) | ||
const expressionToken = node.quasis.slice(0, -1).map(n => tokenStore.getTokenAfter(n)) | ||
const quasiTokens = node.quasis | ||
.slice(1) | ||
.map((n) => tokenStore.getFirstToken(n)) | ||
const expressionToken = node.quasis | ||
.slice(0, -1) | ||
.map((n) => tokenStore.getTokenAfter(n)) | ||
@@ -1647,3 +1910,3 @@ setOffset(quasiTokens, 0, firstToken) | ||
TryStatement (node) { | ||
TryStatement(node) { | ||
const tryToken = tokenStore.getFirstToken(node) | ||
@@ -1668,3 +1931,3 @@ const tryBlockToken = tokenStore.getFirstToken(node.block) | ||
UpdateExpression (node) { | ||
UpdateExpression(node) { | ||
const firstToken = tokenStore.getFirstToken(node) | ||
@@ -1676,7 +1939,12 @@ const nextToken = tokenStore.getTokenAfter(firstToken) | ||
VariableDeclaration (node) { | ||
processNodeList(node.declarations, tokenStore.getFirstToken(node), null, 1) | ||
VariableDeclaration(node) { | ||
processNodeList( | ||
node.declarations, | ||
tokenStore.getFirstToken(node), | ||
null, | ||
1 | ||
) | ||
}, | ||
VariableDeclarator (node) { | ||
VariableDeclarator(node) { | ||
if (node.init != null) { | ||
@@ -1691,3 +1959,3 @@ const idToken = tokenStore.getFirstToken(node) | ||
'WhileStatement, WithStatement' (node) { | ||
'WhileStatement, WithStatement'(node) { | ||
const firstToken = tokenStore.getFirstToken(node) | ||
@@ -1702,3 +1970,3 @@ const leftParenToken = tokenStore.getTokenAfter(firstToken) | ||
YieldExpression (node) { | ||
YieldExpression(node) { | ||
if (node.argument != null) { | ||
@@ -1715,3 +1983,3 @@ const yieldToken = tokenStore.getFirstToken(node) | ||
// Process semicolons. | ||
':statement' (node) { | ||
':statement'(node) { | ||
const firstToken = tokenStore.getFirstToken(node) | ||
@@ -1729,3 +1997,7 @@ const lastToken = tokenStore.getLastToken(node) | ||
const prevToken = tokenStore.getTokenBefore(firstToken) | ||
if (info != null && isSemicolon(prevToken) && prevToken.loc.end.line === firstToken.loc.start.line) { | ||
if ( | ||
info != null && | ||
isSemicolon(prevToken) && | ||
prevToken.loc.end.line === firstToken.loc.start.line | ||
) { | ||
offsets.set(prevToken, info) | ||
@@ -1737,3 +2009,3 @@ } | ||
// `:expression` does not match with MetaProperty and TemplateLiteral as a bug: https://github.com/estools/esquery/pull/59 | ||
':expression, MetaProperty, TemplateLiteral' (node) { | ||
':expression, MetaProperty, TemplateLiteral'(node) { | ||
let leftToken = tokenStore.getTokenBefore(node) | ||
@@ -1754,3 +2026,3 @@ let rightToken = tokenStore.getTokenAfter(node) | ||
// Ignore tokens of unknown nodes. | ||
'*:exit' (node) { | ||
'*:exit'(node) { | ||
if (!KNOWN_NODES.has(node.type)) { | ||
@@ -1762,11 +2034,11 @@ ignore(node) | ||
// Top-level process. | ||
Program (node) { | ||
Program(node) { | ||
const firstToken = node.tokens[0] | ||
const isScriptTag = ( | ||
const isScriptTag = | ||
firstToken != null && | ||
firstToken.type === 'Punctuator' && | ||
firstToken.value === '<script>' | ||
) | ||
const baseIndent = | ||
isScriptTag ? (options.indentSize * options.baseIndent) : 0 | ||
const baseIndent = isScriptTag | ||
? options.indentSize * options.baseIndent | ||
: 0 | ||
@@ -1777,3 +2049,3 @@ for (const statement of node.body) { | ||
}, | ||
"VElement[parent.type!='VElement']" (node) { | ||
"VElement[parent.type!='VElement']"(node) { | ||
processTopLevelNode(node, 0) | ||
@@ -1783,3 +2055,3 @@ }, | ||
// Do validation. | ||
":matches(Program, VElement[parent.type!='VElement']):exit" (node) { | ||
":matches(Program, VElement[parent.type!='VElement']):exit"(node) { | ||
let comments = [] | ||
@@ -1792,3 +2064,6 @@ let tokensOnSameLine = [] | ||
for (const token of tokenStore.getTokens(node, ITERATION_OPTS)) { | ||
if (tokensOnSameLine.length === 0 || tokensOnSameLine[0].loc.start.line === token.loc.start.line) { | ||
if ( | ||
tokensOnSameLine.length === 0 || | ||
tokensOnSameLine[0].loc.start.line === token.loc.start.line | ||
) { | ||
// This is on the same line (or the first token). | ||
@@ -1800,3 +2075,4 @@ tokensOnSameLine.push(token) | ||
comments.push(tokensOnSameLine[0]) | ||
isBesideMultilineToken = last(tokensOnSameLine).loc.end.line === token.loc.start.line | ||
isBesideMultilineToken = | ||
last(tokensOnSameLine).loc.end.line === token.loc.start.line | ||
tokensOnSameLine = [token] | ||
@@ -1809,3 +2085,4 @@ } else { | ||
} | ||
isBesideMultilineToken = last(tokensOnSameLine).loc.end.line === token.loc.start.line | ||
isBesideMultilineToken = | ||
last(tokensOnSameLine).loc.end.line === token.loc.start.line | ||
tokensOnSameLine = [token] | ||
@@ -1812,0 +2089,0 @@ comments = [] |
@@ -21,2 +21,4 @@ /** | ||
* @typedef {import('vue-eslint-parser').AST.ESLintNode} ESLintNode | ||
* | ||
* @typedef {import('vue-eslint-parser').AST.ESLintArrowFunctionExpression | { type: 'ArrowFunctionExpression', body: BlockStatement | Expression } } ArrowFunctionExpression | ||
*/ | ||
@@ -41,2 +43,7 @@ | ||
*/ | ||
/** | ||
* @typedef { { name: string, groupName: string, node: Literal | TemplateLiteral } } ComponentArrayPropertyData | ||
* @typedef { { name: string, groupName: string, node: Identifier | Literal | TemplateLiteral } } ComponentObjectPropertyData | ||
* @typedef { ComponentArrayPropertyData | ComponentObjectPropertyData } ComponentPropertyData | ||
*/ | ||
@@ -295,15 +302,72 @@ // ------------------------------------------------------------------------------ | ||
/** | ||
* Check whether the given attribute has their attribute value. | ||
* @param {ASTNode} node The attribute node to check. | ||
* @returns {boolean} `true` if the attribute has their value. | ||
* Check whether the given directive attribute has their empty value (`=""`). | ||
* @param {ASTNode} node The directive attribute node to check. | ||
* @param {RuleContext} context The rule context to use parser services. | ||
* @returns {boolean} `true` if the directive attribute has their empty value (`=""`). | ||
*/ | ||
hasAttributeValue (node) { | ||
isEmptyValueDirective (node, context) { | ||
assert(node && node.type === 'VAttribute') | ||
return ( | ||
node.value != null && | ||
(node.value.expression != null || node.value.syntaxError != null) | ||
) | ||
if (node.value == null) { | ||
return false | ||
} | ||
if (node.value.expression != null) { | ||
return false | ||
} | ||
let valueText = context.getSourceCode().getText(node.value) | ||
if ((valueText[0] === '"' || valueText[0] === "'") && valueText[0] === valueText[valueText.length - 1]) { | ||
// quoted | ||
valueText = valueText.slice(1, -1) | ||
} | ||
if (!valueText) { | ||
// empty | ||
return true | ||
} | ||
return false | ||
}, | ||
/** | ||
* Check whether the given directive attribute has their empty expression value (e.g. `=" "`, `="/* */"`). | ||
* @param {ASTNode} node The directive attribute node to check. | ||
* @param {RuleContext} context The rule context to use parser services. | ||
* @returns {boolean} `true` if the directive attribute has their empty expression value. | ||
*/ | ||
isEmptyExpressionValueDirective (node, context) { | ||
assert(node && node.type === 'VAttribute') | ||
if (node.value == null) { | ||
return false | ||
} | ||
if (node.value.expression != null) { | ||
return false | ||
} | ||
const valueNode = node.value | ||
const tokenStore = context.parserServices.getTemplateBodyTokenStore() | ||
let quote1 = null | ||
let quote2 = null | ||
// `node.value` may be only comments, so cannot get the correct tokens with `tokenStore.getTokens(node.value)`. | ||
for (const token of tokenStore.getTokens(node)) { | ||
if (token.range[1] <= valueNode.range[0]) { | ||
continue | ||
} | ||
if (valueNode.range[1] <= token.range[0]) { | ||
// empty | ||
return true | ||
} | ||
if (!quote1 && token.type === 'Punctuator' && (token.value === '"' || token.value === "'")) { | ||
quote1 = token | ||
continue | ||
} | ||
if (!quote2 && quote1 && token.type === 'Punctuator' && (token.value === quote1.value)) { | ||
quote2 = token | ||
continue | ||
} | ||
// not empty | ||
return false | ||
} | ||
// empty | ||
return true | ||
}, | ||
/** | ||
* Get the attribute which has the given name. | ||
@@ -640,2 +704,3 @@ * @param {ASTNode} node The start tag node to check. | ||
* - `onSetupFunctionEnter` ... Event when setup function found. | ||
* - `onRenderFunctionEnter` ... Event when render function found. | ||
* | ||
@@ -659,6 +724,31 @@ * @param {RuleContext} context The ESLint rule context object. | ||
/** | ||
* @param {ObjectExpression} node | ||
*/ | ||
vueVisitor.ObjectExpression = (node) => { | ||
const type = getVueObjectType(context, node) | ||
if (type) { | ||
vueStack = { node, type, parent: vueStack } | ||
vueStack = { | ||
node, | ||
type, | ||
parent: vueStack, | ||
get functional () { | ||
/** @type {Property} */ | ||
const functional = node.properties.find( | ||
(p) => | ||
p.type === 'Property' && | ||
getStaticPropertyName(p) === 'functional' | ||
) | ||
if (!functional) { | ||
return false | ||
} | ||
if ( | ||
functional.value.type === 'Literal' && | ||
functional.value.value === false | ||
) { | ||
return false | ||
} | ||
return true | ||
} | ||
} | ||
callVisitor('onVueObjectEnter', node) | ||
@@ -675,12 +765,20 @@ } | ||
} | ||
if (visitor.onSetupFunctionEnter) { | ||
vueVisitor['Property[value.type=/^(Arrow)?FunctionExpression$/] > :function'] = (node) => { | ||
if (visitor.onSetupFunctionEnter || visitor.onRenderFunctionEnter) { | ||
vueVisitor[ | ||
'Property[value.type=/^(Arrow)?FunctionExpression$/] > :function' | ||
] = (node) => { | ||
/** @type {Property} */ | ||
const prop = node.parent | ||
if (vueStack && prop.parent === vueStack.node) { | ||
if (getStaticPropertyName(prop) === 'setup' && prop.value === node) { | ||
if (vueStack && prop.parent === vueStack.node && prop.value === node) { | ||
const name = getStaticPropertyName(prop) | ||
if (name === 'setup') { | ||
callVisitor('onSetupFunctionEnter', node) | ||
} else if (name === 'render') { | ||
callVisitor('onRenderFunctionEnter', node) | ||
} | ||
} | ||
callVisitor('Property[value.type=/^(Arrow)?FunctionExpression$/] > :function', node) | ||
callVisitor( | ||
'Property[value.type=/^(Arrow)?FunctionExpression$/] > :function', | ||
node | ||
) | ||
} | ||
@@ -751,10 +849,13 @@ } | ||
* Return generator with all properties | ||
* @param {ASTNode} node Node to check | ||
* @param {Set} groups Name of parent group | ||
* @param {ObjectExpression} node Node to check | ||
* @param {Set<string>} groups Name of parent group | ||
* @returns {IterableIterator<ComponentPropertyData>} | ||
*/ | ||
* iterateProperties (node, groups) { | ||
const nodes = node.properties.filter(p => p.type === 'Property' && groups.has(getStaticPropertyName(p.key))) | ||
for (const item of nodes) { | ||
const name = getStaticPropertyName(item.key) | ||
if (!name) continue | ||
for (const item of node.properties) { | ||
if (item.type !== 'Property') { | ||
continue | ||
} | ||
const name = getStaticPropertyName(item) | ||
if (!name || !groups.has(name)) continue | ||
@@ -775,4 +876,5 @@ if (item.value.type === 'ArrayExpression') { | ||
* Return generator with all elements inside ArrayExpression | ||
* @param {ASTNode} node Node to check | ||
* @param {ArrayExpression} node Node to check | ||
* @param {string} groupName Name of parent group | ||
* @returns {IterableIterator<ComponentArrayPropertyData>} | ||
*/ | ||
@@ -782,6 +884,7 @@ * iterateArrayExpression (node, groupName) { | ||
for (const item of node.elements) { | ||
const name = getStaticPropertyName(item) | ||
if (name) { | ||
const obj = { name, groupName, node: item } | ||
yield obj | ||
if (item.type === 'Literal' || item.type === 'TemplateLiteral') { | ||
const name = getStaticPropertyName(item) | ||
if (name) { | ||
yield { name, groupName, node: item } | ||
} | ||
} | ||
@@ -793,12 +896,35 @@ } | ||
* Return generator with all elements inside ObjectExpression | ||
* @param {ASTNode} node Node to check | ||
* @param {ObjectExpression} node Node to check | ||
* @param {string} groupName Name of parent group | ||
* @returns {IterableIterator<ComponentObjectPropertyData>} | ||
*/ | ||
* iterateObjectExpression (node, groupName) { | ||
assert(node.type === 'ObjectExpression') | ||
let usedGetter | ||
for (const item of node.properties) { | ||
const name = getStaticPropertyName(item) | ||
if (name) { | ||
const obj = { name, groupName, node: item.key } | ||
yield obj | ||
if (item.type === 'Property') { | ||
const key = item.key | ||
if (key.type === 'Identifier' || key.type === 'Literal' || key.type === 'TemplateLiteral') { | ||
const name = getStaticPropertyName(item) | ||
if (name) { | ||
if (item.kind === 'set') { | ||
// find getter pair | ||
if (!usedGetter) { usedGetter = new Set() } | ||
if (node.properties.some(item2 => { | ||
if (item2.type === 'Property' && item2.kind === 'get' && !usedGetter.has(item2)) { | ||
const getterName = getStaticPropertyName(item2) | ||
if (getterName === name) { | ||
usedGetter.add(item2) | ||
return true | ||
} | ||
} | ||
return false | ||
})) { | ||
// has getter pair | ||
continue | ||
} | ||
} | ||
yield { name, groupName, node: key } | ||
} | ||
} | ||
} | ||
@@ -810,4 +936,5 @@ } | ||
* Return generator with all elements inside FunctionExpression | ||
* @param {ASTNode} node Node to check | ||
* @param {FunctionExpression} node Node to check | ||
* @param {string} groupName Name of parent group | ||
* @returns {IterableIterator<ComponentObjectPropertyData>} | ||
*/ | ||
@@ -827,9 +954,11 @@ * iterateFunctionExpression (node, groupName) { | ||
* Return generator with all elements inside ArrowFunctionExpression | ||
* @param {ASTNode} node Node to check | ||
* @param {ArrowFunctionExpression} node Node to check | ||
* @param {string} groupName Name of parent group | ||
* @returns {IterableIterator<ComponentObjectPropertyData>} | ||
*/ | ||
* iterateArrowFunctionExpression (node, groupName) { | ||
assert(node.type === 'ArrowFunctionExpression') | ||
if (node.body.type === 'BlockStatement') { | ||
for (const item of node.body.body) { | ||
const body = node.body | ||
if (body.type === 'BlockStatement') { | ||
for (const item of body.body) { | ||
if (item.type === 'ReturnStatement' && item.argument && item.argument.type === 'ObjectExpression') { | ||
@@ -839,4 +968,4 @@ yield * this.iterateObjectExpression(item.argument, groupName) | ||
} | ||
} else if (node.body.type === 'ObjectExpression') { | ||
yield * this.iterateObjectExpression(node.body, groupName) | ||
} else if (body.type === 'ObjectExpression') { | ||
yield * this.iterateObjectExpression(body, groupName) | ||
} | ||
@@ -843,0 +972,0 @@ }, |
@@ -25,3 +25,3 @@ const RE_REGEXP_CHAR = /[\\^$.*+?()[\]{}|]/gu | ||
* @param {string} string The string to convert. | ||
* @returns {string} Returns the `RegExp`. | ||
* @returns {RegExp} Returns the `RegExp`. | ||
*/ | ||
@@ -36,5 +36,15 @@ function toRegExp(string) { | ||
/** | ||
* Checks whether given string is regexp string | ||
* @param {string} string | ||
* @returns {boolean} | ||
*/ | ||
function isRegExp(string) { | ||
return Boolean(RE_REGEXP_STR.exec(string)) | ||
} | ||
module.exports = { | ||
escape, | ||
toRegExp | ||
toRegExp, | ||
isRegExp | ||
} |
{ | ||
"name": "eslint-plugin-vue", | ||
"version": "7.0.0-alpha.5", | ||
"version": "7.0.0-alpha.6", | ||
"description": "Official ESLint plugin for Vue.js", | ||
@@ -11,2 +11,4 @@ "main": "lib/index.js", | ||
"debug": "mocha --inspect-brk \"tests/lib/**/*.js\" --reporter dot --timeout 60000", | ||
"cover": "npm run cover:test && npm run cover:report", | ||
"cover:test": "nyc npm run test:base -- --timeout 60000", | ||
"cover:report": "nyc report --reporter=html", | ||
@@ -13,0 +15,0 @@ "lint": "eslint . --rulesdir eslint-internal-rules", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
653986
186
19638