Skip to content

Commit

Permalink
Add support for imported types in SFC macros (#2134)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed May 15, 2023
1 parent 4c5fe34 commit 9abf469
Show file tree
Hide file tree
Showing 44 changed files with 1,882 additions and 466 deletions.
18 changes: 10 additions & 8 deletions docs/.vitepress/config.ts
Expand Up @@ -73,7 +73,7 @@ const sidebarCategories = [
}
]

const categorizedRules: DefaultTheme.SidebarGroup[] = []
const categorizedRules: DefaultTheme.SidebarItem[] = []
for (const { title, categoryIds } of sidebarCategories) {
const categoryRules = rules
.filter((rule) => rule.meta.docs.categories && !rule.meta.deprecated)
Expand All @@ -84,8 +84,10 @@ for (const { title, categoryIds } of sidebarCategories) {
)
const children: DefaultTheme.SidebarItem[] = categoryRules
.filter(({ ruleId }) => {
const exists = categorizedRules.some(({ items }) =>
items.some(({ text: alreadyRuleId }) => alreadyRuleId === ruleId)
const exists = categorizedRules.some(
({ items }) =>
items &&
items.some(({ text: alreadyRuleId }) => alreadyRuleId === ruleId)
)
return !exists
})
Expand All @@ -101,16 +103,16 @@ for (const { title, categoryIds } of sidebarCategories) {
}
categorizedRules.push({
text: title,
collapsible: false,
collapsed: false,
items: children
})
}

const extraCategories: DefaultTheme.SidebarGroup[] = []
const extraCategories: DefaultTheme.SidebarItem[] = []
if (uncategorizedRules.length > 0) {
extraCategories.push({
text: 'Uncategorized',
collapsible: false,
collapsed: false,
items: uncategorizedRules.map(({ ruleId, name }) => ({
text: ruleId,
link: `/rules/${name}`
Expand All @@ -120,7 +122,7 @@ if (uncategorizedRules.length > 0) {
if (uncategorizedExtensionRule.length > 0) {
extraCategories.push({
text: 'Extension Rules',
collapsible: false,
collapsed: false,
items: uncategorizedExtensionRule.map(({ ruleId, name }) => ({
text: ruleId,
link: `/rules/${name}`
Expand All @@ -130,7 +132,7 @@ if (uncategorizedExtensionRule.length > 0) {
if (deprecatedRules.length > 0) {
extraCategories.push({
text: 'Deprecated',
collapsible: false,
collapsed: false,
items: deprecatedRules.map(({ ruleId, name }) => ({
text: ruleId,
link: `/rules/${name}`
Expand Down
15 changes: 9 additions & 6 deletions lib/rules/no-restricted-props.js
Expand Up @@ -109,14 +109,17 @@ module.exports = {
option.message ||
`Using \`${prop.propName}\` props is not allowed.`
context.report({
node: prop.key,
node: prop.type !== 'infer-type' ? prop.key : prop.node,
messageId: 'restrictedProp',
data: { message },
suggest: createSuggest(
prop.key,
option,
withDefaultsProps && withDefaultsProps[prop.propName]
)
suggest:
prop.type !== 'infer-type'
? createSuggest(
prop.key,
option,
withDefaultsProps && withDefaultsProps[prop.propName]
)
: null
})
break
}
Expand Down
4 changes: 2 additions & 2 deletions lib/rules/no-unused-properties.js
Expand Up @@ -30,7 +30,7 @@ const {
* @typedef {object} ComponentNonObjectPropertyData
* @property {string} name
* @property {GroupName} groupName
* @property {'array' | 'type'} type
* @property {'array' | 'type' | 'infer-type'} type
* @property {ASTNode} node
*
* @typedef { ComponentNonObjectPropertyData | ComponentObjectPropertyData } ComponentPropertyData
Expand Down Expand Up @@ -423,7 +423,7 @@ module.exports = {
type: prop.type,
name: prop.propName,
groupName: 'props',
node: prop.key
node: prop.type !== 'infer-type' ? prop.key : prop.node
})
}
}
Expand Down
32 changes: 19 additions & 13 deletions lib/rules/padding-lines-in-component-definition.js
Expand Up @@ -6,6 +6,7 @@

/**
* @typedef {import('../utils').ComponentProp} ComponentProp
* @typedef {import('../utils').ComponentEmit} ComponentEmit
* @typedef {import('../utils').GroupName} GroupName
*/

Expand Down Expand Up @@ -33,10 +34,19 @@ function isComma(node) {
}

/**
* @param {string} nodeType
* @typedef {Exclude<ComponentProp | ComponentEmit, {type:'infer-type'}> & { node: {type: 'Property' | 'SpreadElement'} }} ValidComponentPropOrEmit
*/
function isValidProperties(nodeType) {
return ['Property', 'SpreadElement'].includes(nodeType)
/**
* @template {ComponentProp | ComponentEmit} T
* @param {T} propOrEmit
* @returns {propOrEmit is ValidComponentPropOrEmit & T}
*/
function isValidProperties(propOrEmit) {
return Boolean(
propOrEmit.type !== 'infer-type' &&
propOrEmit.node &&
['Property', 'SpreadElement'].includes(propOrEmit.node.type)
)
}

/**
Expand Down Expand Up @@ -320,11 +330,9 @@ module.exports = {
}),
utils.defineScriptSetupVisitor(context, {
onDefinePropsEnter(_, props) {
const propNodes = /** @type {(Property | SpreadElement)[]} */ (
props
.filter((prop) => prop.node && isValidProperties(prop.node.type))
.map((prop) => prop.node)
)
const propNodes = props
.filter(isValidProperties)
.map((prop) => prop.node)

const withinOption = parseOption(options, OptionKeys.WithinOption)
const propsOption = withinOption && parseOption(withinOption, 'props')
Expand All @@ -337,11 +345,9 @@ module.exports = {
)
},
onDefineEmitsEnter(_, emits) {
const emitNodes = /** @type {(Property | SpreadElement)[]} */ (
emits
.filter((emit) => emit.node && isValidProperties(emit.node.type))
.map((emit) => emit.node)
)
const emitNodes = emits
.filter(isValidProperties)
.map((emit) => emit.node)

const withinOption = parseOption(options, OptionKeys.WithinOption)
const emitsOption = withinOption && parseOption(withinOption, 'emits')
Expand Down
3 changes: 3 additions & 0 deletions lib/rules/prefer-prop-type-boolean-first.js
Expand Up @@ -67,6 +67,9 @@ module.exports = {
* @param {import('../utils').ComponentProp} prop
*/
function checkProperty(prop) {
if (prop.type !== 'object') {
return
}
const { value } = prop
if (!value) {
return
Expand Down
23 changes: 14 additions & 9 deletions lib/rules/require-emit-validator.js
Expand Up @@ -34,32 +34,37 @@ module.exports = {
* @param {ComponentEmit} emit
*/
function checker(emit) {
if (emit.type !== 'object' && emit.type !== 'array') {
return
}
const { value, node, emitName } = emit
const hasType =
!!value &&
(value.type === 'ArrowFunctionExpression' ||
/** @type {Expression|null} */
let value = null
let hasType = false
if (emit.type === 'object') {
value = emit.value
hasType =
value.type === 'ArrowFunctionExpression' ||
value.type === 'FunctionExpression' ||
// validator may from outer scope
value.type === 'Identifier')
value.type === 'Identifier'
} else if (emit.type !== 'array') {
return
}

if (!hasType) {
const { node, emitName } = emit
const name =
emitName ||
(node.type === 'Identifier' && node.name) ||
'Unknown emit'

if (value && value.type === 'Literal' && value.value === null) {
const valueNode = value
context.report({
node,
messageId: 'skipped',
data: { name },
suggest: [
{
messageId: 'emptyValidation',
fix: (fixer) => fixer.replaceText(value, '() => true')
fix: (fixer) => fixer.replaceText(valueNode, '() => true')
}
]
})
Expand Down
5 changes: 4 additions & 1 deletion lib/rules/require-explicit-emits.js
Expand Up @@ -429,7 +429,10 @@ module.exports = {
function buildSuggest(define, emits, nameWithLoc, context) {
const emitsKind =
define.type === 'ObjectExpression' ? '`emits` option' : '`defineEmits`'
const certainEmits = emits.filter((e) => e.key)
const certainEmits = emits.filter(
/** @returns {e is ComponentEmit & {type:'array'|'object'}} */
(e) => e.type === 'array' || e.type === 'object'
)
if (certainEmits.length > 0) {
const last = certainEmits[certainEmits.length - 1]
return [
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/require-prop-comment.js
Expand Up @@ -62,7 +62,7 @@ module.exports = {
*/
function verifyProps(props) {
for (const prop of props) {
if (!prop.propName) {
if (!prop.propName || prop.type === 'infer-type') {
continue
}

Expand Down
2 changes: 1 addition & 1 deletion lib/rules/require-prop-type-constructor.js
Expand Up @@ -79,7 +79,7 @@ module.exports = {
/** @param {ComponentProp[]} props */
function verifyProps(props) {
for (const prop of props) {
if (!prop.value || prop.propName == null) {
if (prop.type !== 'object' || prop.propName == null) {
continue
}
if (
Expand Down
5 changes: 3 additions & 2 deletions lib/rules/require-prop-types.js
Expand Up @@ -51,12 +51,12 @@ module.exports = {
if (prop.type !== 'object' && prop.type !== 'array') {
return
}
const { value, node, propName } = prop
let hasType = true

if (!value) {
if (prop.type === 'array') {
hasType = false
} else {
const { value } = prop
switch (value.type) {
case 'ObjectExpression': {
// foo: {
Expand All @@ -77,6 +77,7 @@ module.exports = {
}

if (!hasType) {
const { node, propName } = prop
const name =
propName ||
(node.type === 'Identifier' && node.name) ||
Expand Down
14 changes: 8 additions & 6 deletions lib/rules/require-valid-default-prop.js
Expand Up @@ -7,9 +7,11 @@ const utils = require('../utils')
const { capitalize } = require('../utils/casing')

/**
* @typedef {import('../utils').ComponentProp} ComponentProp
* @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
* @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp
* @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp
* @typedef {import('../utils').ComponentInferTypeProp} ComponentInferTypeProp
* @typedef {import('../utils').ComponentUnknownProp} ComponentUnknownProp
* @typedef {import('../utils').VueObjectData} VueObjectData
*/
Expand Down Expand Up @@ -108,7 +110,7 @@ module.exports = {
*/
/**
* @typedef {object} PropDefaultFunctionContext
* @property {ComponentObjectProp | ComponentTypeProp} prop
* @property {ComponentObjectProp | ComponentTypeProp | ComponentInferTypeProp} prop
* @property {Set<string>} types
* @property {FunctionValueType} default
*/
Expand Down Expand Up @@ -225,7 +227,7 @@ module.exports = {

/**
* @param {*} node
* @param {ComponentObjectProp | ComponentTypeProp} prop
* @param {ComponentObjectProp | ComponentTypeProp | ComponentInferTypeProp} prop
* @param {Iterable<string>} expectedTypeNames
*/
function report(node, prop, expectedTypeNames) {
Expand All @@ -245,7 +247,7 @@ module.exports = {
}

/**
* @param {(ComponentObjectDefineProp | ComponentTypeProp)[]} props
* @param {(ComponentObjectDefineProp | ComponentTypeProp | ComponentInferTypeProp)[]} props
* @param { { [key: string]: Expression | undefined } } withDefaults
*/
function processPropDefs(props, withDefaults) {
Expand Down Expand Up @@ -394,15 +396,15 @@ module.exports = {
}),
utils.defineScriptSetupVisitor(context, {
onDefinePropsEnter(node, baseProps) {
/** @type {(ComponentObjectDefineProp | ComponentTypeProp)[]} */
const props = baseProps.filter(
/**
* @param {ComponentObjectProp | ComponentArrayProp | ComponentTypeProp | ComponentUnknownProp} prop
* @returns {prop is ComponentObjectDefineProp | ComponentTypeProp}
* @param {ComponentProp} prop
* @returns {prop is ComponentObjectDefineProp | ComponentInferTypeProp | ComponentTypeProp}
*/
(prop) =>
Boolean(
prop.type === 'type' ||
prop.type === 'infer-type' ||
(prop.type === 'object' &&
prop.value.type === 'ObjectExpression')
)
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/return-in-emits-validator.js
Expand Up @@ -65,7 +65,7 @@ module.exports = {
*/
function processEmits(emits) {
for (const emit of emits) {
if (!emit.value) {
if (emit.type !== 'object' || !emit.value) {
continue
}
if (
Expand Down
4 changes: 2 additions & 2 deletions lib/utils/indent-ts.js
Expand Up @@ -12,7 +12,7 @@ const {
isClosingBracketToken,
isOpeningBracketToken
} = require('@eslint-community/eslint-utils')
const { isTypeNode } = require('./ts-ast-utils')
const { isTypeNode } = require('./ts-utils')

/**
* @typedef {import('../../typings/eslint-plugin-vue/util-types/indent-helper').TSNodeListener} TSNodeListener
Expand Down Expand Up @@ -227,7 +227,7 @@ function defineVisitor({
processSemicolons(node)
},
/**
* @param {TSESTreeNode} node
* @param {ASTNode} node
*/
// eslint-disable-next-line complexity -- ignore
'*[type=/^TS/]'(node) {
Expand Down

0 comments on commit 9abf469

Please sign in to comment.