vue hello world项目
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

336 lines
10 KiB

3 years ago
const { findVariable } = require('eslint-utils')
/**
* @typedef {import('@typescript-eslint/types').TSESTree.TypeNode} TypeNode
* @typedef {import('@typescript-eslint/types').TSESTree.TSInterfaceBody} TSInterfaceBody
* @typedef {import('@typescript-eslint/types').TSESTree.TSTypeLiteral} TSTypeLiteral
* @typedef {import('@typescript-eslint/types').TSESTree.Parameter} TSESTreeParameter
* @typedef {import('@typescript-eslint/types').TSESTree.Node} Node
*
*/
/**
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeProp} ComponentTypeProp
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeEmit} ComponentTypeEmit
*/
module.exports = {
isTypeNode,
getComponentPropsFromTypeDefine,
getComponentEmitsFromTypeDefine
}
/**
* @param {Node | ASTNode} node
* @returns {node is TypeNode}
*/
function isTypeNode(node) {
return (
node.type === 'TSAnyKeyword' ||
node.type === 'TSArrayType' ||
node.type === 'TSBigIntKeyword' ||
node.type === 'TSBooleanKeyword' ||
node.type === 'TSConditionalType' ||
node.type === 'TSConstructorType' ||
node.type === 'TSFunctionType' ||
node.type === 'TSImportType' ||
node.type === 'TSIndexedAccessType' ||
node.type === 'TSInferType' ||
node.type === 'TSIntersectionType' ||
node.type === 'TSIntrinsicKeyword' ||
node.type === 'TSLiteralType' ||
node.type === 'TSMappedType' ||
node.type === 'TSNamedTupleMember' ||
node.type === 'TSNeverKeyword' ||
node.type === 'TSNullKeyword' ||
node.type === 'TSNumberKeyword' ||
node.type === 'TSObjectKeyword' ||
node.type === 'TSOptionalType' ||
node.type === 'TSRestType' ||
node.type === 'TSStringKeyword' ||
node.type === 'TSSymbolKeyword' ||
node.type === 'TSTemplateLiteralType' ||
node.type === 'TSThisType' ||
node.type === 'TSTupleType' ||
node.type === 'TSTypeLiteral' ||
node.type === 'TSTypeOperator' ||
node.type === 'TSTypePredicate' ||
node.type === 'TSTypeQuery' ||
node.type === 'TSTypeReference' ||
node.type === 'TSUndefinedKeyword' ||
node.type === 'TSUnionType' ||
node.type === 'TSUnknownKeyword' ||
node.type === 'TSVoidKeyword'
)
}
/**
* @param {TypeNode} node
* @returns {node is TSTypeLiteral}
*/
function isTSTypeLiteral(node) {
return node.type === 'TSTypeLiteral'
}
/**
* @param {TypeNode} node
* @returns {node is TSFunctionType}
*/
function isTSFunctionType(node) {
return node.type === 'TSFunctionType'
}
/**
* Get all props by looking at all component's properties
* @param {RuleContext} context The ESLint rule context object.
* @param {TypeNode} propsNode Type with props definition
* @return {ComponentTypeProp[]} Array of component props
*/
function getComponentPropsFromTypeDefine(context, propsNode) {
/** @type {TSInterfaceBody | TSTypeLiteral|null} */
const defNode = resolveQualifiedType(context, propsNode, isTSTypeLiteral)
if (!defNode) {
return []
}
return [...extractRuntimeProps(context, defNode)]
}
/**
* Get all emits by looking at all component's properties
* @param {RuleContext} context The ESLint rule context object.
* @param {TypeNode} emitsNode Type with emits definition
* @return {ComponentTypeEmit[]} Array of component emits
*/
function getComponentEmitsFromTypeDefine(context, emitsNode) {
/** @type {TSInterfaceBody | TSTypeLiteral | TSFunctionType | null} */
const defNode = resolveQualifiedType(
context,
emitsNode,
(n) => isTSTypeLiteral(n) || isTSFunctionType(n)
)
if (!defNode) {
return []
}
return [...extractRuntimeEmits(defNode)]
}
/**
* @see https://github.com/vuejs/vue-next/blob/253ca2729d808fc051215876aa4af986e4caa43c/packages/compiler-sfc/src/compileScript.ts#L1512
* @param {RuleContext} context The ESLint rule context object.
* @param {TSTypeLiteral | TSInterfaceBody} node
* @returns {IterableIterator<ComponentTypeProp>}
*/
function* extractRuntimeProps(context, node) {
const members = node.type === 'TSTypeLiteral' ? node.members : node.body
for (const m of members) {
if (
(m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature') &&
(m.key.type === 'Identifier' || m.key.type === 'Literal')
) {
let type
if (m.type === 'TSMethodSignature') {
type = ['Function']
} else if (m.typeAnnotation) {
type = inferRuntimeType(context, m.typeAnnotation.typeAnnotation)
}
yield {
type: 'type',
key: /** @type {Identifier | Literal} */ (m.key),
propName: m.key.type === 'Identifier' ? m.key.name : `${m.key.value}`,
value: null,
node: /** @type {TSPropertySignature | TSMethodSignature} */ (m),
required: !m.optional,
types: type || [`null`]
}
}
}
}
/**
* @see https://github.com/vuejs/vue-next/blob/348c3b01e56383ffa70b180d1376fdf4ac12e274/packages/compiler-sfc/src/compileScript.ts#L1632
* @param {TSTypeLiteral | TSInterfaceBody | TSFunctionType} node
* @returns {IterableIterator<ComponentTypeEmit>}
*/
function* extractRuntimeEmits(node) {
if (node.type === 'TSTypeLiteral' || node.type === 'TSInterfaceBody') {
const members = node.type === 'TSTypeLiteral' ? node.members : node.body
for (const t of members) {
if (t.type === 'TSCallSignatureDeclaration') {
yield* extractEventNames(
t.params[0],
/** @type {TSCallSignatureDeclaration} */ (t)
)
}
}
return
} else {
yield* extractEventNames(node.params[0], node)
}
/**
* @param {TSESTreeParameter} eventName
* @param {TSCallSignatureDeclaration | TSFunctionType} member
* @returns {IterableIterator<ComponentTypeEmit>}
*/
function* extractEventNames(eventName, member) {
if (
eventName &&
eventName.type === 'Identifier' &&
eventName.typeAnnotation &&
eventName.typeAnnotation.type === 'TSTypeAnnotation'
) {
const typeNode = eventName.typeAnnotation.typeAnnotation
if (
typeNode.type === 'TSLiteralType' &&
typeNode.literal.type === 'Literal'
) {
const emitName = String(typeNode.literal.value)
yield {
type: 'type',
key: /** @type {TSLiteralType} */ (typeNode),
emitName,
value: null,
node: member
}
} else if (typeNode.type === 'TSUnionType') {
for (const t of typeNode.types) {
if (t.type === 'TSLiteralType' && t.literal.type === 'Literal') {
const emitName = String(t.literal.value)
yield {
type: 'type',
key: /** @type {TSLiteralType} */ (t),
emitName,
value: null,
node: member
}
}
}
}
}
}
}
/**
* @see https://github.com/vuejs/vue-next/blob/253ca2729d808fc051215876aa4af986e4caa43c/packages/compiler-sfc/src/compileScript.ts#L425
*
* @param {RuleContext} context The ESLint rule context object.
* @param {TypeNode} node
* @param {(n: TypeNode)=> boolean } qualifier
*/
function resolveQualifiedType(context, node, qualifier) {
if (qualifier(node)) {
return node
}
if (node.type === 'TSTypeReference' && node.typeName.type === 'Identifier') {
const refName = node.typeName.name
const variable = findVariable(context.getScope(), refName)
if (variable && variable.defs.length === 1) {
const def = variable.defs[0]
if (def.node.type === 'TSInterfaceDeclaration') {
return /** @type {any} */ (def.node).body
}
if (def.node.type === 'TSTypeAliasDeclaration') {
const typeAnnotation = /** @type {any} */ (def.node).typeAnnotation
return qualifier(typeAnnotation) ? typeAnnotation : null
}
}
}
}
/**
* @param {RuleContext} context The ESLint rule context object.
* @param {TypeNode} node
* @param {Set<TypeNode>} [checked]
* @returns {string[]}
*/
function inferRuntimeType(context, node, checked = new Set()) {
switch (node.type) {
case 'TSStringKeyword':
return ['String']
case 'TSNumberKeyword':
return ['Number']
case 'TSBooleanKeyword':
return ['Boolean']
case 'TSObjectKeyword':
return ['Object']
case 'TSTypeLiteral':
return ['Object']
case 'TSFunctionType':
return ['Function']
case 'TSArrayType':
case 'TSTupleType':
return ['Array']
case 'TSLiteralType':
switch (node.literal.type) {
//@ts-ignore ?
case 'StringLiteral':
return ['String']
//@ts-ignore ?
case 'BooleanLiteral':
return ['Boolean']
//@ts-ignore ?
case 'NumericLiteral':
//@ts-ignore ?
// eslint-disable-next-line no-fallthrough
case 'BigIntLiteral':
return ['Number']
default:
return [`null`]
}
case 'TSTypeReference':
if (node.typeName.type === 'Identifier') {
const variable = findVariable(context.getScope(), node.typeName.name)
if (variable && variable.defs.length === 1) {
const def = variable.defs[0]
if (def.node.type === 'TSInterfaceDeclaration') {
return [`Object`]
}
if (def.node.type === 'TSTypeAliasDeclaration') {
const typeAnnotation = /** @type {any} */ (def.node).typeAnnotation
if (!checked.has(typeAnnotation)) {
checked.add(typeAnnotation)
return inferRuntimeType(context, typeAnnotation, checked)
}
}
}
switch (node.typeName.name) {
case 'Array':
case 'Function':
case 'Object':
case 'Set':
case 'Map':
case 'WeakSet':
case 'WeakMap':
case 'Date':
return [node.typeName.name]
case 'Record':
case 'Partial':
case 'Readonly':
case 'Pick':
case 'Omit':
case 'Exclude':
case 'Extract':
case 'Required':
case 'InstanceType':
return ['Object']
}
}
return [`null`]
case 'TSUnionType':
const set = new Set()
for (const t of node.types) {
for (const tt of inferRuntimeType(context, t, checked)) {
set.add(tt)
}
}
return [...set]
case 'TSIntersectionType':
return ['Object']
default:
return [`null`] // no runtime check
}
}