Add components

This commit is contained in:
2025-09-29 11:24:36 +02:00
parent 6d1050c6cb
commit 620d4144b5
3748 changed files with 902194 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
/**
* Capitalize a string.
* @param {string} str
*/
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
/**
* Checks whether the given string has symbols.
* @param {string} str
*/
function hasSymbols(str) {
return /[!"#%&'()*+,./:;<=>?@[\\\]^`{|}]/u.exec(str) // without " ", "$", "-" and "_"
}
/**
* Checks whether the given string has upper.
* @param {string} str
*/
function hasUpper(str) {
return /[A-Z]/u.exec(str)
}
/**
* Convert text to kebab-case
* @param {string} str Text to be converted
* @return {string}
*/
function kebabCase(str) {
return str
.replace(/_/gu, '-')
.replace(/\B([A-Z])/gu, '-$1')
.toLowerCase()
}
/**
* Checks whether the given string is kebab-case.
* @param {string} str
*/
function isKebabCase(str) {
return (
!hasUpper(str) &&
!hasSymbols(str) &&
!str.startsWith('-') && // starts with hyphen is not kebab-case
!/_|--|\s/u.test(str)
)
}
/**
* Convert text to snake_case
* @param {string} str Text to be converted
* @return {string}
*/
function snakeCase(str) {
return str
.replace(/\B([A-Z])/gu, '_$1')
.replace(/-/gu, '_')
.toLowerCase()
}
/**
* Checks whether the given string is snake_case.
* @param {string} str
*/
function isSnakeCase(str) {
return !hasUpper(str) && !hasSymbols(str) && !/-|__|\s/u.test(str)
}
/**
* Convert text to camelCase
* @param {string} str Text to be converted
* @return {string} Converted string
*/
function camelCase(str) {
if (isPascalCase(str)) {
return str.charAt(0).toLowerCase() + str.slice(1)
}
return str.replace(/[-_](\w)/gu, (_, c) => (c ? c.toUpperCase() : ''))
}
/**
* Checks whether the given string is camelCase.
* @param {string} str
*/
function isCamelCase(str) {
return !hasSymbols(str) && !/^[A-Z]/u.test(str) && !/-|_|\s/u.test(str)
}
/**
* Convert text to PascalCase
* @param {string} str Text to be converted
* @return {string} Converted string
*/
function pascalCase(str) {
return capitalize(camelCase(str))
}
/**
* Checks whether the given string is PascalCase.
* @param {string} str
*/
function isPascalCase(str) {
return !hasSymbols(str) && !/^[a-z]/u.test(str) && !/-|_|\s/u.test(str)
}
const convertersMap = {
'kebab-case': kebabCase,
snake_case: snakeCase,
camelCase,
PascalCase: pascalCase
}
const checkersMap = {
'kebab-case': isKebabCase,
snake_case: isSnakeCase,
camelCase: isCamelCase,
PascalCase: isPascalCase
}
/**
* Return case checker
* @param { 'camelCase' | 'kebab-case' | 'PascalCase' | 'snake_case' } name type of checker to return ('camelCase', 'kebab-case', 'PascalCase')
* @return {isKebabCase|isCamelCase|isPascalCase|isSnakeCase}
*/
function getChecker(name) {
return checkersMap[name] || isPascalCase
}
/**
* Return case converter
* @param { 'camelCase' | 'kebab-case' | 'PascalCase' | 'snake_case' } name type of converter to return ('camelCase', 'kebab-case', 'PascalCase')
* @return {kebabCase|camelCase|pascalCase|snakeCase}
*/
function getConverter(name) {
return convertersMap[name] || pascalCase
}
module.exports = {
allowedCaseOptions: ['camelCase', 'kebab-case', 'PascalCase'],
/**
* Return case converter
* @param {string} name type of converter to return ('camelCase', 'kebab-case', 'PascalCase')
* @return {kebabCase|camelCase|pascalCase|snakeCase}
*/
getConverter,
/**
* Return case checker
* @param {string} name type of checker to return ('camelCase', 'kebab-case', 'PascalCase')
* @return {isKebabCase|isCamelCase|isPascalCase|isSnakeCase}
*/
getChecker,
/**
* Return case exact converter.
* If the converted result is not the correct case, the original value is returned.
* @param { 'camelCase' | 'kebab-case' | 'PascalCase' | 'snake_case' } name type of converter to return ('camelCase', 'kebab-case', 'PascalCase')
* @return {kebabCase|camelCase|pascalCase|snakeCase}
*/
getExactConverter(name) {
const converter = getConverter(name)
const checker = getChecker(name)
return (str) => {
const result = converter(str)
return checker(result) ? result : str /* cannot convert */
}
},
camelCase,
pascalCase,
kebabCase,
snakeCase,
isCamelCase,
isPascalCase,
isKebabCase,
isSnakeCase,
capitalize
}

View File

@@ -0,0 +1,21 @@
/**
* @param {Comment} node
* @returns {boolean}
*/
const isJSDocComment = (node) =>
node.type === 'Block' &&
node.value.charAt(0) === '*' &&
node.value.charAt(1) !== '*'
/**
* @param {Comment} node
* @returns {boolean}
*/
const isBlockComment = (node) =>
node.type === 'Block' &&
(node.value.charAt(0) !== '*' || node.value.charAt(1) === '*')
module.exports = {
isJSDocComment,
isBlockComment
}

View File

@@ -0,0 +1,31 @@
[
"acronym",
"applet",
"basefont",
"bgsound",
"big",
"blink",
"center",
"dir",
"font",
"frame",
"frameset",
"isindex",
"keygen",
"listing",
"marquee",
"menuitem",
"multicol",
"nextid",
"nobr",
"noembed",
"noframes",
"param",
"plaintext",
"rb",
"rtc",
"spacer",
"strike",
"tt",
"xmp"
]

View File

@@ -0,0 +1,251 @@
/**
* @typedef { { exceptions?: string[] } } CommentParserConfig
* @typedef { (comment: ParsedHTMLComment) => void } HTMLCommentVisitor
* @typedef { { includeDirectives?: boolean } } CommentVisitorOption
*
* @typedef { Token & { type: 'HTMLCommentOpen' } } HTMLCommentOpen
* @typedef { Token & { type: 'HTMLCommentOpenDecoration' } } HTMLCommentOpenDecoration
* @typedef { Token & { type: 'HTMLCommentValue' } } HTMLCommentValue
* @typedef { Token & { type: 'HTMLCommentClose' } } HTMLCommentClose
* @typedef { Token & { type: 'HTMLCommentCloseDecoration' } } HTMLCommentCloseDecoration
* @typedef { { open: HTMLCommentOpen, openDecoration: HTMLCommentOpenDecoration | null, value: HTMLCommentValue | null, closeDecoration: HTMLCommentCloseDecoration | null, close: HTMLCommentClose } } ParsedHTMLComment
*/
const utils = require('./')
const COMMENT_DIRECTIVE = /^\s*eslint-(?:en|dis)able/
const IE_CONDITIONAL_IF = /^\[if\s+/
const IE_CONDITIONAL_ENDIF = /\[endif]$/
/** @type { 'HTMLCommentOpen' } */
const TYPE_HTML_COMMENT_OPEN = 'HTMLCommentOpen'
/** @type { 'HTMLCommentOpenDecoration' } */
const TYPE_HTML_COMMENT_OPEN_DECORATION = 'HTMLCommentOpenDecoration'
/** @type { 'HTMLCommentValue' } */
const TYPE_HTML_COMMENT_VALUE = 'HTMLCommentValue'
/** @type { 'HTMLCommentClose' } */
const TYPE_HTML_COMMENT_CLOSE = 'HTMLCommentClose'
/** @type { 'HTMLCommentCloseDecoration' } */
const TYPE_HTML_COMMENT_CLOSE_DECORATION = 'HTMLCommentCloseDecoration'
/**
* @param {HTMLComment} comment
* @returns {boolean}
*/
function isCommentDirective(comment) {
return COMMENT_DIRECTIVE.test(comment.value)
}
/**
* @param {HTMLComment} comment
* @returns {boolean}
*/
function isIEConditionalComment(comment) {
return (
IE_CONDITIONAL_IF.test(comment.value) ||
IE_CONDITIONAL_ENDIF.test(comment.value)
)
}
/**
* Define HTML comment parser
*
* @param {SourceCode} sourceCode The source code instance.
* @param {CommentParserConfig | null} config The config.
* @returns { (node: Token) => (ParsedHTMLComment | null) } HTML comment parser.
*/
function defineParser(sourceCode, config) {
config = config || {}
const exceptions = config.exceptions || []
/**
* Get a open decoration string from comment contents.
* @param {string} contents comment contents
* @returns {string} decoration string
*/
function getOpenDecoration(contents) {
let decoration = ''
for (const exception of exceptions) {
const length = exception.length
let index = 0
while (contents.startsWith(exception, index)) {
index += length
}
const exceptionLength = index
if (decoration.length < exceptionLength) {
decoration = contents.slice(0, exceptionLength)
}
}
return decoration
}
/**
* Get a close decoration string from comment contents.
* @param {string} contents comment contents
* @returns {string} decoration string
*/
function getCloseDecoration(contents) {
let decoration = ''
for (const exception of exceptions) {
const length = exception.length
let index = contents.length
while (contents.endsWith(exception, index)) {
index -= length
}
const exceptionLength = contents.length - index
if (decoration.length < exceptionLength) {
decoration = contents.slice(index)
}
}
return decoration
}
/**
* Parse HTMLComment.
* @param {Token} node a comment token
* @returns {ParsedHTMLComment | null} the result of HTMLComment tokens.
*/
return function parseHTMLComment(node) {
if (node.type !== 'HTMLComment') {
// Is not HTMLComment
return null
}
const htmlCommentText = sourceCode.getText(node)
if (
!htmlCommentText.startsWith('<!--') ||
!htmlCommentText.endsWith('-->')
) {
// Is not normal HTML Comment
// e.g. Error Code: "abrupt-closing-of-empty-comment", "incorrectly-closed-comment"
return null
}
let valueText = htmlCommentText.slice(4, -3)
const openDecorationText = getOpenDecoration(valueText)
valueText = valueText.slice(openDecorationText.length)
const firstCharIndex = valueText.search(/\S/)
const beforeSpace =
firstCharIndex >= 0 ? valueText.slice(0, firstCharIndex) : valueText
valueText = valueText.slice(beforeSpace.length)
const closeDecorationText = getCloseDecoration(valueText)
if (closeDecorationText) {
valueText = valueText.slice(0, -closeDecorationText.length)
}
const lastCharIndex = valueText.search(/\S\s*$/)
const afterSpace =
lastCharIndex >= 0 ? valueText.slice(lastCharIndex + 1) : valueText
if (afterSpace) {
valueText = valueText.slice(0, -afterSpace.length)
}
let tokenIndex = node.range[0]
/**
* @param {string} type
* @param {string} value
* @returns {any}
*/
const createToken = (type, value) => {
/** @type {Range} */
const range = [tokenIndex, tokenIndex + value.length]
tokenIndex = range[1]
/** @type {SourceLocation} */
let loc
return {
type,
value,
range,
get loc() {
if (loc) {
return loc
}
return (loc = {
start: sourceCode.getLocFromIndex(range[0]),
end: sourceCode.getLocFromIndex(range[1])
})
}
}
}
/** @type {HTMLCommentOpen} */
const open = createToken(TYPE_HTML_COMMENT_OPEN, '<!--')
/** @type {HTMLCommentOpenDecoration | null} */
const openDecoration = openDecorationText
? createToken(TYPE_HTML_COMMENT_OPEN_DECORATION, openDecorationText)
: null
tokenIndex += beforeSpace.length
/** @type {HTMLCommentValue | null} */
const value = valueText
? createToken(TYPE_HTML_COMMENT_VALUE, valueText)
: null
tokenIndex += afterSpace.length
/** @type {HTMLCommentCloseDecoration | null} */
const closeDecoration = closeDecorationText
? createToken(TYPE_HTML_COMMENT_CLOSE_DECORATION, closeDecorationText)
: null
/** @type {HTMLCommentClose} */
const close = createToken(TYPE_HTML_COMMENT_CLOSE, '-->')
return {
/** HTML comment open (`<!--`) */
open,
/** decoration of the start of HTML comments. (`*****` when `<!--*****`) */
openDecoration,
/** value of HTML comment. whitespaces and other tokens are not included. */
value,
/** decoration of the end of HTML comments. (`*****` when `*****-->`) */
closeDecoration,
/** HTML comment close (`-->`) */
close
}
}
}
/**
* Define HTML comment visitor
*
* @param {RuleContext} context The rule context.
* @param {CommentParserConfig | null} config The config.
* @param {HTMLCommentVisitor} visitHTMLComment The HTML comment visitor.
* @param {CommentVisitorOption} [visitorOption] The option for visitor.
* @returns {RuleListener} HTML comment visitor.
*/
function defineVisitor(context, config, visitHTMLComment, visitorOption) {
return {
Program(node) {
visitorOption = visitorOption || {}
if (utils.hasInvalidEOF(node)) {
return
}
if (!node.templateBody) {
return
}
const parse = defineParser(context.getSourceCode(), config)
for (const comment of node.templateBody.comments) {
if (comment.type !== 'HTMLComment') {
continue
}
if (!visitorOption.includeDirectives && isCommentDirective(comment)) {
// ignore directives
continue
}
if (isIEConditionalComment(comment)) {
// ignore IE conditional
continue
}
const tokens = parse(comment)
if (tokens) {
visitHTMLComment(tokens)
}
}
}
}
}
module.exports = {
defineVisitor
}

View File

@@ -0,0 +1,116 @@
[
"a",
"abbr",
"address",
"area",
"article",
"aside",
"audio",
"b",
"base",
"bdi",
"bdo",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"cite",
"code",
"col",
"colgroup",
"data",
"datalist",
"dd",
"del",
"details",
"dfn",
"dialog",
"div",
"dl",
"dt",
"em",
"embed",
"fencedframe",
"fieldset",
"figcaption",
"figure",
"footer",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hgroup",
"hr",
"html",
"i",
"iframe",
"img",
"input",
"ins",
"kbd",
"label",
"legend",
"li",
"link",
"main",
"map",
"mark",
"menu",
"meta",
"meter",
"nav",
"noscript",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"picture",
"pre",
"progress",
"q",
"rp",
"rt",
"ruby",
"s",
"samp",
"script",
"search",
"section",
"select",
"selectedcontent",
"slot",
"small",
"source",
"span",
"strong",
"style",
"sub",
"summary",
"sup",
"table",
"tbody",
"td",
"template",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"u",
"ul",
"var",
"video",
"wbr"
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,117 @@
'use strict'
/**
* Check whether the given token is a wildcard.
* @param {Token|undefined|null} token The token to check.
* @returns {boolean} `true` if the token is a wildcard.
*/
function isWildcard(token) {
return token != null && token.type === 'Punctuator' && token.value === '*'
}
/**
* Check whether the given token is a question.
* @param {Token|undefined|null} token The token to check.
* @returns {boolean} `true` if the token is a question.
*/
function isQuestion(token) {
return token != null && token.type === 'Punctuator' && token.value === '?'
}
/**
* Check whether the given token is an extends keyword.
* @param {Token|undefined|null} token The token to check.
* @returns {boolean} `true` if the token is an extends keywordn.
*/
function isExtendsKeyword(token) {
return token != null && token.type === 'Keyword' && token.value === 'extends'
}
/**
* Check whether the given token is a whitespace.
* @param {Token|undefined|null} token The token to check.
* @returns {boolean} `true` if the token is a whitespace.
*/
function isNotWhitespace(token) {
return (
token != null &&
token.type !== 'HTMLWhitespace' &&
(token.type !== 'JSXText' || !!token.value.trim())
)
}
/**
* Check whether the given token is a comment.
* @param {Token|undefined|null} token The token to check.
* @returns {boolean} `true` if the token is a comment.
*/
function isComment(token) {
return (
token != null &&
(token.type === 'Block' ||
token.type === 'Line' ||
token.type === 'Shebang' ||
(typeof token.type ===
'string' /* Although acorn supports new tokens, espree may not yet support new tokens.*/ &&
token.type.endsWith('Comment')))
)
}
/**
* Check whether the given token is a comment.
* @param {Token|undefined|null} token The token to check.
* @returns {boolean} `false` if the token is a comment.
*/
function isNotComment(token) {
return (
token != null &&
token.type !== 'Block' &&
token.type !== 'Line' &&
token.type !== 'Shebang' &&
!(
typeof token.type ===
'string' /* Although acorn supports new tokens, espree may not yet support new tokens.*/ &&
token.type.endsWith('Comment')
)
)
}
/**
* Check whether the given node is not an empty text node.
* @param {ASTNode} node The node to check.
* @returns {boolean} `false` if the token is empty text node.
*/
function isNotEmptyTextNode(node) {
return !(node.type === 'VText' && node.value.trim() === '')
}
/**
* Check whether the given token is a pipe operator.
* @param {Token|undefined|null} token The token to check.
* @returns {boolean} `true` if the token is a pipe operator.
*/
function isPipeOperator(token) {
return token != null && token.type === 'Punctuator' && token.value === '|'
}
/**
* Get the last element.
* @template T
* @param {T[]} xs The array to get the last element.
* @returns {T | undefined} The last element or undefined.
*/
function last(xs) {
return xs.length === 0 ? undefined : xs[xs.length - 1]
}
module.exports = {
isWildcard,
isQuestion,
isExtendsKeyword,
isNotWhitespace,
isComment,
isNotComment,
isNotEmptyTextNode,
isPipeOperator,
last
}

3601
slider/node_modules/eslint-plugin-vue/lib/utils/index.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
[
"a",
"abbr",
"audio",
"b",
"bdi",
"bdo",
"canvas",
"cite",
"code",
"data",
"del",
"dfn",
"em",
"i",
"iframe",
"ins",
"kbd",
"label",
"map",
"mark",
"noscript",
"object",
"output",
"picture",
"q",
"ruby",
"s",
"samp",
"small",
"span",
"strong",
"sub",
"sup",
"svg",
"time",
"u",
"var",
"video"
]

View File

@@ -0,0 +1,66 @@
[
"abstract",
"arguments",
"await",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"double",
"else",
"enum",
"eval",
"export",
"extends",
"false",
"final",
"finally",
"float",
"for",
"function",
"goto",
"if",
"implements",
"import",
"in",
"instanceof",
"int",
"interface",
"let",
"long",
"native",
"new",
"null",
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"super",
"switch",
"synchronized",
"this",
"throw",
"throws",
"transient",
"true",
"try",
"typeof",
"var",
"void",
"volatile",
"while",
"with",
"yield"
]

View File

@@ -0,0 +1,304 @@
[
"unidentified",
"alt",
"alt-graph",
"caps-lock",
"control",
"fn",
"fn-lock",
"meta",
"num-lock",
"scroll-lock",
"shift",
"symbol",
"symbol-lock",
"hyper",
"super",
"enter",
"tab",
"arrow-down",
"arrow-left",
"arrow-right",
"arrow-up",
"end",
"home",
"page-down",
"page-up",
"backspace",
"clear",
"copy",
"cr-sel",
"cut",
"delete",
"erase-eof",
"ex-sel",
"insert",
"paste",
"redo",
"undo",
"accept",
"again",
"attn",
"cancel",
"context-menu",
"escape",
"execute",
"find",
"help",
"pause",
"select",
"zoom-in",
"zoom-out",
"brightness-down",
"brightness-up",
"eject",
"log-off",
"power",
"print-screen",
"hibernate",
"standby",
"wake-up",
"all-candidates",
"alphanumeric",
"code-input",
"compose",
"convert",
"dead",
"final-mode",
"group-first",
"group-last",
"group-next",
"group-previous",
"mode-change",
"next-candidate",
"non-convert",
"previous-candidate",
"process",
"single-candidate",
"hangul-mode",
"hanja-mode",
"junja-mode",
"eisu",
"hankaku",
"hiragana",
"hiragana-katakana",
"kana-mode",
"kanji-mode",
"katakana",
"romaji",
"zenkaku",
"zenkaku-hankaku",
"f1",
"f2",
"f3",
"f4",
"f5",
"f6",
"f7",
"f8",
"f9",
"f10",
"f11",
"f12",
"soft1",
"soft2",
"soft3",
"soft4",
"channel-down",
"channel-up",
"close",
"mail-forward",
"mail-reply",
"mail-send",
"media-close",
"media-fast-forward",
"media-pause",
"media-play-pause",
"media-record",
"media-rewind",
"media-stop",
"media-track-next",
"media-track-previous",
"new",
"open",
"print",
"save",
"spell-check",
"key11",
"key12",
"audio-balance-left",
"audio-balance-right",
"audio-bass-boost-down",
"audio-bass-boost-toggle",
"audio-bass-boost-up",
"audio-fader-front",
"audio-fader-rear",
"audio-surround-mode-next",
"audio-treble-down",
"audio-treble-up",
"audio-volume-down",
"audio-volume-up",
"audio-volume-mute",
"microphone-toggle",
"microphone-volume-down",
"microphone-volume-up",
"microphone-volume-mute",
"speech-correction-list",
"speech-input-toggle",
"launch-application1",
"launch-application2",
"launch-calendar",
"launch-contacts",
"launch-mail",
"launch-media-player",
"launch-music-player",
"launch-phone",
"launch-screen-saver",
"launch-spreadsheet",
"launch-web-browser",
"launch-web-cam",
"launch-word-processor",
"browser-back",
"browser-favorites",
"browser-forward",
"browser-home",
"browser-refresh",
"browser-search",
"browser-stop",
"app-switch",
"call",
"camera",
"camera-focus",
"end-call",
"go-back",
"go-home",
"headset-hook",
"last-number-redial",
"notification",
"manner-mode",
"voice-dial",
"t-v",
"t-v3-d-mode",
"t-v-antenna-cable",
"t-v-audio-description",
"t-v-audio-description-mix-down",
"t-v-audio-description-mix-up",
"t-v-contents-menu",
"t-v-data-service",
"t-v-input",
"t-v-input-component1",
"t-v-input-component2",
"t-v-input-composite1",
"t-v-input-composite2",
"t-v-input-h-d-m-i1",
"t-v-input-h-d-m-i2",
"t-v-input-h-d-m-i3",
"t-v-input-h-d-m-i4",
"t-v-input-v-g-a1",
"t-v-media-context",
"t-v-network",
"t-v-number-entry",
"t-v-power",
"t-v-radio-service",
"t-v-satellite",
"t-v-satellite-b-s",
"t-v-satellite-c-s",
"t-v-satellite-toggle",
"t-v-terrestrial-analog",
"t-v-terrestrial-digital",
"t-v-timer",
"a-v-r-input",
"a-v-r-power",
"color-f0-red",
"color-f1-green",
"color-f2-yellow",
"color-f3-blue",
"color-f4-grey",
"color-f5-brown",
"closed-caption-toggle",
"dimmer",
"display-swap",
"d-v-r",
"exit",
"favorite-clear0",
"favorite-clear1",
"favorite-clear2",
"favorite-clear3",
"favorite-recall0",
"favorite-recall1",
"favorite-recall2",
"favorite-recall3",
"favorite-store0",
"favorite-store1",
"favorite-store2",
"favorite-store3",
"guide",
"guide-next-day",
"guide-previous-day",
"info",
"instant-replay",
"link",
"list-program",
"live-content",
"lock",
"media-apps",
"media-last",
"media-skip-backward",
"media-skip-forward",
"media-step-backward",
"media-step-forward",
"media-top-menu",
"navigate-in",
"navigate-next",
"navigate-out",
"navigate-previous",
"next-favorite-channel",
"next-user-profile",
"on-demand",
"pairing",
"pin-p-down",
"pin-p-move",
"pin-p-toggle",
"pin-p-up",
"play-speed-down",
"play-speed-reset",
"play-speed-up",
"random-toggle",
"rc-low-battery",
"record-speed-next",
"rf-bypass",
"scan-channels-toggle",
"screen-mode-next",
"settings",
"split-screen-toggle",
"s-t-b-input",
"s-t-b-power",
"subtitle",
"teletext",
"video-mode-next",
"wink",
"zoom-toggle",
"audio-volume-down",
"audio-volume-up",
"audio-volume-mute",
"browser-back",
"browser-forward",
"channel-down",
"channel-up",
"context-menu",
"eject",
"end",
"enter",
"home",
"media-fast-forward",
"media-play",
"media-play-pause",
"media-record",
"media-rewind",
"media-stop",
"media-next-track",
"media-pause",
"media-previous-track",
"power",
"unidentified"
]

View File

@@ -0,0 +1,99 @@
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
/** @type { { [key: number]: string } } */
module.exports = {
8: 'backspace',
9: 'tab',
13: 'enter',
16: 'shift',
17: 'ctrl',
18: 'alt',
19: 'pause', // windows
20: 'caps-lock',
27: 'escape',
32: 'space', // Vue.js specially key name.
33: 'page-up',
34: 'page-down',
35: 'end',
36: 'home',
37: 'arrow-left',
38: 'arrow-up',
39: 'arrow-right',
40: 'arrow-down',
45: 'insert', // windows
46: 'delete',
// If mistakenly use it in Vue.js 2.x, it will be irreversibly broken. Therefore, it will not be autofix.
// '48': '0',
// '49': '1',
// '50': '2',
// '51': '3',
// '52': '4',
// '53': '5',
// '54': '6',
// '55': '7',
// '56': '8',
// '57': '9',
65: 'a',
66: 'b',
67: 'c',
68: 'd',
69: 'e',
70: 'f',
71: 'g',
72: 'h',
73: 'i',
74: 'j',
75: 'k',
76: 'l',
77: 'm',
78: 'n',
79: 'o',
80: 'p',
81: 'q',
82: 'r',
83: 's',
84: 't',
85: 'u',
86: 'v',
87: 'w',
88: 'x',
89: 'y',
90: 'z',
// The key value may change depending on the OS.
// '91': 'meta' ,// Win: 'os'?
// '92': 'meta', // Win: 'meta' Mac: ?
// '93': 'meta', // Win: 'context-menu' Mac: 'meta'
// Cannot determine numpad with key.
// '96': 'numpad-0',
// '97': 'numpad-1',
// '98': 'numpad-2',
// '99': 'numpad-3',
// '100': 'numpad-4',
// '101': 'numpad-5',
// '102': 'numpad-6',
// '103': 'numpad-7',
// '104': 'numpad-8',
// '105': 'numpad-9',
// '106': 'multiply',
// '107': 'add',
// '109': 'subtract',
// '110': 'decimal',
// '111': 'divide',
112: 'f1',
113: 'f2',
114: 'f3',
115: 'f4',
116: 'f5',
117: 'f6',
118: 'f7',
119: 'f8',
120: 'f9',
121: 'f10',
122: 'f11',
123: 'f12',
144: 'num-lock',
145: 'scroll-lock'
}

View File

@@ -0,0 +1,34 @@
[
"math",
"maction",
"annotation",
"annotation-xml",
"menclose",
"merror",
"mfenced",
"mfrac",
"mi",
"mmultiscripts",
"mn",
"mo",
"mover",
"mpadded",
"mphantom",
"mprescripts",
"mroot",
"mrow",
"ms",
"semantics",
"mspace",
"msqrt",
"mstyle",
"msub",
"msup",
"msubsup",
"mtable",
"mtd",
"mtext",
"mtr",
"munder",
"munderover"
]

View File

@@ -0,0 +1,787 @@
/**
* @author Yosuke Ota
* @copyright 2021 Yosuke Ota. All rights reserved.
* See LICENSE file in root directory for full license.
*/
'use strict'
const utils = require('./index')
const eslintUtils = require('@eslint-community/eslint-utils')
/**
* @typedef {import('./style-variables').StyleVariablesContext} StyleVariablesContext
*/
/**
* @typedef {object} IHasPropertyOption
* @property {boolean} [unknownCallAsAny]
*/
/**
* @typedef {object} NestPropertyNodeForExpression
* @property {'expression'} type
* @property {MemberExpression} node
*
* @typedef {object} NestPropertyNodeForPattern
* @property {'pattern'} type
* @property {MemberExpression | Identifier | ObjectPattern | ArrayPattern} node
*
* @typedef {NestPropertyNodeForExpression | NestPropertyNodeForPattern} NestPropertyNode
*/
/**
* @typedef {object} IPropertyReferences
* @property { (name: string, option?: IHasPropertyOption) => boolean } hasProperty
* @property { () => Map<string, {nodes:ASTNode[]}> } allProperties Get all properties.
* @property { (name: string) => IPropertyReferences } getNest Get the nesting property references.
* @property { (name: string) => Iterable<NestPropertyNode> } getNestNodes Get the nesting property nodes.
*/
/** @type {IPropertyReferences} */
const ANY = {
hasProperty: () => true,
allProperties: () => new Map(),
getNest: () => ANY,
getNestNodes: () => []
}
/** @type {IPropertyReferences} */
const NEVER = {
hasProperty: () => false,
allProperties: () => new Map(),
getNest: () => NEVER,
getNestNodes: () => []
}
/**
* @param {RuleContext} context
* @param {Identifier} id
* @returns {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration | null}
*/
function findFunction(context, id) {
const calleeVariable = utils.findVariableByIdentifier(context, id)
if (!calleeVariable) {
return null
}
if (calleeVariable.defs.length === 1) {
const def = calleeVariable.defs[0]
if (def.node.type === 'FunctionDeclaration') {
return def.node
}
if (
def.type === 'Variable' &&
def.parent.kind === 'const' &&
def.node.init
) {
if (
def.node.init.type === 'FunctionExpression' ||
def.node.init.type === 'ArrowFunctionExpression'
) {
return def.node.init
}
if (def.node.init.type === 'Identifier') {
return findFunction(context, def.node.init)
}
}
}
return null
}
module.exports = {
definePropertyReferenceExtractor,
mergePropertyReferences
}
/**
* @param {RuleContext} context The rule context.
*/
function definePropertyReferenceExtractor(
context,
{ unknownMemberAsUnreferenced = false, returnAsUnreferenced = false } = {}
) {
/** @type {Map<Expression, IPropertyReferences>} */
const cacheForExpression = new Map()
/** @type {Map<Pattern, IPropertyReferences>} */
const cacheForPattern = new Map()
/** @type {Map<FunctionExpression | ArrowFunctionExpression | FunctionDeclaration, Map<number, IPropertyReferences>>} */
const cacheForFunction = new Map()
/** @type {{ toRefNodes: Set<ESNode>, toRefsNodes: Set<ESNode>} | null} */
let toRefSet = null
let isFunctionalTemplate = false
const templateBody = context.getSourceCode().ast.templateBody
if (templateBody) {
isFunctionalTemplate = utils.hasAttribute(templateBody, 'functional')
}
function getToRefSet() {
if (toRefSet) {
return toRefSet
}
const tracker = new eslintUtils.ReferenceTracker(
context.getSourceCode().scopeManager.scopes[0]
)
const toRefNodes = new Set()
for (const { node } of utils.iterateReferencesTraceMap(tracker, {
[eslintUtils.ReferenceTracker.ESM]: true,
toRef: {
[eslintUtils.ReferenceTracker.CALL]: true
}
})) {
toRefNodes.add(node)
}
const toRefsNodes = new Set()
for (const { node } of utils.iterateReferencesTraceMap(tracker, {
[eslintUtils.ReferenceTracker.ESM]: true,
toRefs: {
[eslintUtils.ReferenceTracker.CALL]: true
}
})) {
toRefsNodes.add(node)
}
return (toRefSet = { toRefNodes, toRefsNodes })
}
/**
* Collects the property references for member expr.
* @implements {IPropertyReferences}
*/
class PropertyReferencesForMember {
/**
*
* @param {MemberExpression} node
* @param {string} name
* @param {boolean} withInTemplate
*/
constructor(node, name, withInTemplate) {
this.node = node
this.name = name
this.withInTemplate = withInTemplate
}
/**
* @param {string} name
*/
hasProperty(name) {
return name === this.name
}
allProperties() {
return new Map([[this.name, { nodes: [this.node.property] }]])
}
/**
* @param {string} name
* @returns {IPropertyReferences}
*/
getNest(name) {
return name === this.name
? extractFromExpression(this.node, this.withInTemplate)
: NEVER
}
/**
* @param {string} name
* @returns {Iterable<NestPropertyNodeForExpression>}
*/
*getNestNodes(name) {
if (name === this.name) {
yield {
type: 'expression',
node: this.node
}
}
}
}
/**
* Collects the property references for object.
* @implements {IPropertyReferences}
*/
class PropertyReferencesForObject {
constructor() {
/** @type {Record<string, AssignmentProperty[]>} */
this.properties = Object.create(null)
}
/**
* @param {string} name
*/
hasProperty(name) {
return Boolean(this.properties[name])
}
allProperties() {
const result = new Map()
for (const [name, nodes] of Object.entries(this.properties)) {
result.set(name, { nodes: nodes.map((node) => node.key) })
}
return result
}
/**
* @param {string} name
* @returns {IPropertyReferences}
*/
getNest(name) {
const properties = this.properties[name]
return properties
? mergePropertyReferences(
properties.map((property) => getNestFromPattern(property.value))
)
: NEVER
}
/**
* @param {string} name
* @returns {Iterable<NestPropertyNodeForPattern>}
*/
*getNestNodes(name) {
const properties = this.properties[name]
if (!properties) {
return
}
const values = properties.map((property) => property.value)
let node
while ((node = values.shift())) {
if (
node.type === 'Identifier' ||
node.type === 'MemberExpression' ||
node.type === 'ObjectPattern' ||
node.type === 'ArrayPattern'
) {
yield {
type: 'pattern',
node
}
} else if (node.type === 'AssignmentPattern') {
values.unshift(node.left)
}
}
return properties ? properties.map((p) => p.value) : []
}
}
/**
* @param {Pattern} pattern
* @returns {IPropertyReferences}
*/
function getNestFromPattern(pattern) {
if (pattern.type === 'ObjectPattern') {
return extractFromObjectPattern(pattern)
}
if (pattern.type === 'Identifier') {
return extractFromIdentifier(pattern)
} else if (pattern.type === 'AssignmentPattern') {
return getNestFromPattern(pattern.left)
}
return ANY
}
/**
* Extract the property references from Expression.
* @param {Identifier | MemberExpression | ChainExpression | ThisExpression | CallExpression} node
* @param {boolean} withInTemplate
* @returns {IPropertyReferences}
*/
function extractFromExpression(node, withInTemplate) {
const ref = cacheForExpression.get(node)
if (ref) {
return ref
}
cacheForExpression.set(node, ANY)
const result = extractWithoutCache()
cacheForExpression.set(node, result)
return result
function extractWithoutCache() {
const parent = node.parent
switch (parent.type) {
case 'AssignmentExpression': {
// `({foo} = arg)`
return !withInTemplate &&
parent.right === node &&
parent.operator === '='
? extractFromPattern(parent.left)
: NEVER
}
case 'VariableDeclarator': {
// `const {foo} = arg`
// `const foo = arg`
return !withInTemplate && parent.init === node
? extractFromPattern(parent.id)
: NEVER
}
case 'MemberExpression': {
if (parent.object === node) {
// `arg.foo`
const name = utils.getStaticPropertyName(parent)
if (
name === '$props' &&
parent.parent.type === 'MemberExpression'
) {
// `$props.arg`
const propName = utils.getStaticPropertyName(parent.parent)
if (!propName) return unknownMemberAsUnreferenced ? NEVER : ANY
return new PropertyReferencesForMember(
parent.parent,
propName,
withInTemplate
)
} else if (name) {
return new PropertyReferencesForMember(
parent,
name,
withInTemplate
)
} else {
return unknownMemberAsUnreferenced ? NEVER : ANY
}
}
return NEVER
}
case 'CallExpression': {
const argIndex = parent.arguments.indexOf(node)
// `foo(arg)`
return !withInTemplate && argIndex !== -1
? extractFromCall(parent, argIndex)
: NEVER
}
case 'ChainExpression': {
return extractFromExpression(parent, withInTemplate)
}
case 'ArrowFunctionExpression':
case 'VExpressionContainer':
case 'Property':
case 'ArrayExpression': {
return maybeExternalUsed(parent) ? ANY : NEVER
}
case 'ReturnStatement': {
if (returnAsUnreferenced) {
return NEVER
} else {
return maybeExternalUsed(parent) ? ANY : NEVER
}
}
}
return NEVER
}
/**
* @param {ASTNode} parentTarget
* @returns {boolean}
*/
function maybeExternalUsed(parentTarget) {
if (
parentTarget.type === 'ReturnStatement' ||
parentTarget.type === 'VExpressionContainer'
) {
return true
}
if (parentTarget.type === 'ArrayExpression') {
return maybeExternalUsed(parentTarget.parent)
}
if (parentTarget.type === 'Property') {
return maybeExternalUsed(parentTarget.parent.parent)
}
if (parentTarget.type === 'ArrowFunctionExpression') {
return parentTarget.body === node
}
return false
}
}
/**
* Extract the property references from one parameter of the function.
* @param {Pattern} node
* @returns {IPropertyReferences}
*/
function extractFromPattern(node) {
const ref = cacheForPattern.get(node)
if (ref) {
return ref
}
cacheForPattern.set(node, ANY)
const result = extractWithoutCache()
cacheForPattern.set(node, result)
return result
function extractWithoutCache() {
while (node.type === 'AssignmentPattern') {
node = node.left
}
if (node.type === 'RestElement' || node.type === 'ArrayPattern') {
// cannot check
return NEVER
}
if (node.type === 'ObjectPattern') {
return extractFromObjectPattern(node)
}
if (node.type === 'Identifier') {
return extractFromIdentifier(node)
}
return NEVER
}
}
/**
* Extract the property references from ObjectPattern.
* @param {ObjectPattern} node
* @returns {IPropertyReferences}
*/
function extractFromObjectPattern(node) {
const refs = new PropertyReferencesForObject()
for (const prop of node.properties) {
if (prop.type === 'Property') {
const name = utils.getStaticPropertyName(prop)
if (name) {
const list = refs.properties[name] || (refs.properties[name] = [])
list.push(prop)
} else {
// If cannot trace name, everything is used!
return ANY
}
} else {
// If use RestElement, everything is used!
return ANY
}
}
return refs
}
/**
* Extract the property references from id.
* @param {Identifier} node
* @returns {IPropertyReferences}
*/
function extractFromIdentifier(node) {
const variable = utils.findVariableByIdentifier(context, node)
if (!variable) {
return NEVER
}
return mergePropertyReferences(
variable.references.map((reference) => {
const id = reference.identifier
return extractFromExpression(id, false)
})
)
}
/**
* Extract the property references from call.
* @param {CallExpression} node
* @param {number} argIndex
* @returns {IPropertyReferences}
*/
function extractFromCall(node, argIndex) {
if (node.callee.type !== 'Identifier') {
return {
hasProperty(_name, options) {
return Boolean(options && options.unknownCallAsAny)
},
allProperties: () => new Map(),
getNest: () => ANY,
getNestNodes: () => []
}
}
const fnNode = findFunction(context, node.callee)
if (!fnNode) {
if (argIndex === 0) {
if (getToRefSet().toRefNodes.has(node)) {
return extractFromToRef(node)
} else if (getToRefSet().toRefsNodes.has(node)) {
return extractFromToRefs(node)
}
}
return {
hasProperty(_name, options) {
return Boolean(options && options.unknownCallAsAny)
},
allProperties: () => new Map(),
getNest: () => ANY,
getNestNodes: () => []
}
}
return extractFromFunctionParam(fnNode, argIndex)
}
/**
* Extract the property references from function param.
* @param {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration} node
* @param {number} argIndex
* @returns {IPropertyReferences}
*/
function extractFromFunctionParam(node, argIndex) {
let cacheForIndexes = cacheForFunction.get(node)
if (!cacheForIndexes) {
cacheForIndexes = new Map()
cacheForFunction.set(node, cacheForIndexes)
}
const ref = cacheForIndexes.get(argIndex)
if (ref) {
return ref
}
cacheForIndexes.set(argIndex, NEVER)
const arg = node.params[argIndex]
if (!arg) {
return NEVER
}
const result = extractFromPattern(arg)
cacheForIndexes.set(argIndex, result)
return result
}
/**
* Extract the property references from path.
* @param {string} pathString
* @param {Identifier | Literal | TemplateLiteral} node
* @returns {IPropertyReferences}
*/
function extractFromPath(pathString, node) {
return extractFromSegments(pathString.split('.'))
/**
* @param {string[]} segments
* @returns {IPropertyReferences}
*/
function extractFromSegments(segments) {
if (segments.length === 0) {
return ANY
}
const segmentName = segments[0]
return {
hasProperty: (name) => name === segmentName,
allProperties: () => new Map([[segmentName, { nodes: [node] }]]),
getNest: (name) =>
name === segmentName ? extractFromSegments(segments.slice(1)) : NEVER,
getNestNodes: () => []
}
}
}
/**
* Extract the property references from name literal.
* @param {Expression} node
* @returns {IPropertyReferences}
*/
function extractFromNameLiteral(node) {
const referenceName =
node.type === 'Literal' || node.type === 'TemplateLiteral'
? utils.getStringLiteralValue(node)
: null
return referenceName
? {
hasProperty: (name) => name === referenceName,
allProperties: () => new Map([[referenceName, { nodes: [node] }]]),
getNest: (name) => (name === referenceName ? ANY : NEVER),
getNestNodes: () => []
}
: NEVER
}
/**
* Extract the property references from name.
* @param {string} referenceName
* @param {Expression|SpreadElement} nameNode
* @param { () => IPropertyReferences } [getNest]
* @returns {IPropertyReferences}
*/
function extractFromName(referenceName, nameNode, getNest) {
return {
hasProperty: (name) => name === referenceName,
allProperties: () => new Map([[referenceName, { nodes: [nameNode] }]]),
getNest: (name) =>
name === referenceName ? (getNest ? getNest() : ANY) : NEVER,
getNestNodes: () => []
}
}
/**
* Extract the property references from toRef call.
* @param {CallExpression} node
* @returns {IPropertyReferences}
*/
function extractFromToRef(node) {
const nameNode = node.arguments[1]
const refName =
nameNode &&
(nameNode.type === 'Literal' || nameNode.type === 'TemplateLiteral')
? utils.getStringLiteralValue(nameNode)
: null
if (!refName) {
// unknown name
return ANY
}
return extractFromName(refName, nameNode, () =>
extractFromExpression(node, false).getNest('value')
)
}
/**
* Extract the property references from toRefs call.
* @param {CallExpression} node
* @returns {IPropertyReferences}
*/
function extractFromToRefs(node) {
const base = extractFromExpression(node, false)
return {
hasProperty: (name, option) => base.hasProperty(name, option),
allProperties: () => base.allProperties(),
getNest: (name) => base.getNest(name).getNest('value'),
getNestNodes: (name) => base.getNest(name).getNestNodes('value')
}
}
/**
* Extract the property references from VExpressionContainer.
* @param {VExpressionContainer} node
* @param {object} [options]
* @param {boolean} [options.ignoreGlobals]
* @returns {IPropertyReferences}
*/
function extractFromVExpressionContainer(node, options) {
const ignoreGlobals = options && options.ignoreGlobals
/** @type { (name:string)=>boolean } */
let ignoreRef = () => false
if (ignoreGlobals) {
const globalScope =
context.getSourceCode().scopeManager.globalScope ||
context.getSourceCode().scopeManager.scopes[0]
ignoreRef = (name) => globalScope.set.has(name)
}
/** @type {IPropertyReferences[]} */
const references = []
for (const id of node.references
.filter((ref) => ref.variable == null)
.map((ref) => ref.id)) {
if (ignoreRef(id.name)) {
continue
}
if (isFunctionalTemplate) {
if (id.name === 'props') {
references.push(extractFromExpression(id, true))
}
} else {
const referenceId =
id.name === '$props' &&
id.parent.type === 'MemberExpression' &&
id.parent.property.type === 'Identifier'
? id.parent.property
: id
references.push(
extractFromName(referenceId.name, referenceId, () =>
extractFromExpression(referenceId, true)
)
)
}
}
return mergePropertyReferences(references)
}
/**
* Extract the property references from StyleVariablesContext.
* @param {StyleVariablesContext} ctx
* @returns {IPropertyReferences}
*/
function extractFromStyleVariablesContext(ctx) {
const references = []
for (const { id } of ctx.references) {
references.push(
extractFromName(id.name, id, () => extractFromExpression(id, true))
)
}
return mergePropertyReferences(references)
}
return {
extractFromExpression,
extractFromPattern,
extractFromFunctionParam,
extractFromPath,
extractFromName,
extractFromNameLiteral,
extractFromVExpressionContainer,
extractFromStyleVariablesContext
}
}
/**
* @param {IPropertyReferences[]} references
* @returns {IPropertyReferences}
*/
function mergePropertyReferences(references) {
if (references.length === 0) {
return NEVER
}
if (references.length === 1) {
return references[0]
}
return new PropertyReferencesForMerge(references)
}
/**
* Collects the property references for merge.
* @implements {IPropertyReferences}
*/
class PropertyReferencesForMerge {
/**
* @param {IPropertyReferences[]} references
*/
constructor(references) {
this.references = references
}
/**
* @param {string} name
* @param {IHasPropertyOption} [option]
*/
hasProperty(name, option) {
return this.references.some((ref) => ref.hasProperty(name, option))
}
allProperties() {
const result = new Map()
for (const reference of this.references) {
for (const [name, { nodes }] of reference.allProperties()) {
const r = result.get(name)
if (r) {
r.nodes = [...new Set([...r.nodes, ...nodes])]
} else {
result.set(name, { nodes: [...nodes] })
}
}
}
return result
}
/**
* @param {string} name
* @returns {IPropertyReferences}
*/
getNest(name) {
/** @type {IPropertyReferences[]} */
const nest = []
for (const ref of this.references) {
if (ref.hasProperty(name)) {
nest.push(ref.getNest(name))
}
}
return mergePropertyReferences(nest)
}
/**
* @param {string} name
* @returns {Iterable<NestPropertyNode>}
*/
*getNestNodes(name) {
for (const ref of this.references) {
if (ref.hasProperty(name)) {
yield* ref.getNestNodes(name)
}
}
}
}

View File

@@ -0,0 +1,702 @@
/**
* @author Yosuke Ota
* @copyright 2022 Yosuke Ota. All rights reserved.
* See LICENSE file in root directory for full license.
*/
'use strict'
const utils = require('./index')
const eslintUtils = require('@eslint-community/eslint-utils')
const { definePropertyReferenceExtractor } = require('./property-references')
const { ReferenceTracker } = eslintUtils
/**
* @typedef {object} RefObjectReferenceForExpression
* @property {'expression'} type
* @property {MemberExpression | CallExpression} node
* @property {string} method
* @property {CallExpression} define
* @property {(CallExpression | Identifier | MemberExpression)[]} defineChain Holds the initialization path for assignment of ref objects.
*
* @typedef {object} RefObjectReferenceForPattern
* @property {'pattern'} type
* @property {ObjectPattern} node
* @property {string} method
* @property {CallExpression} define
* @property {(CallExpression | Identifier | MemberExpression)[]} defineChain Holds the initialization path for assignment of ref objects.
*
* @typedef {object} RefObjectReferenceForIdentifier
* @property {'expression' | 'pattern'} type
* @property {Identifier} node
* @property {VariableDeclarator | null} variableDeclarator
* @property {VariableDeclaration | null} variableDeclaration
* @property {string} method
* @property {CallExpression} define
* @property {(CallExpression | Identifier | MemberExpression)[]} defineChain Holds the initialization path for assignment of ref objects.
*
* @typedef {RefObjectReferenceForIdentifier | RefObjectReferenceForExpression | RefObjectReferenceForPattern} RefObjectReference
*/
/**
* @typedef {object} ReactiveVariableReference
* @property {Identifier} node
* @property {boolean} escape Within escape hint (`$$()`)
* @property {VariableDeclaration} variableDeclaration
* @property {string} method
* @property {CallExpression} define
*/
/**
* @typedef {object} RefObjectReferences
* @property {<T extends Identifier | Expression | Pattern | Super> (node: T) =>
* T extends Identifier ?
* RefObjectReferenceForIdentifier | null :
* T extends Expression ?
* RefObjectReferenceForExpression | null :
* T extends Pattern ?
* RefObjectReferenceForPattern | null :
* null} get
*/
/**
* @typedef {object} ReactiveVariableReferences
* @property {(node: Identifier) => ReactiveVariableReference | null} get
*/
const REF_MACROS = [
'$ref',
'$computed',
'$shallowRef',
'$customRef',
'$toRef',
'$'
]
/** @type {WeakMap<Program, RefObjectReferences>} */
const cacheForRefObjectReferences = new WeakMap()
/** @type {WeakMap<Program, ReactiveVariableReferences>} */
const cacheForReactiveVariableReferences = new WeakMap()
/**
* Iterate the call expressions that define the ref object.
* @param {import('eslint').Scope.Scope} globalScope
* @returns {Iterable<{ node: CallExpression, name: string }>}
*/
function* iterateDefineRefs(globalScope) {
const tracker = new ReferenceTracker(globalScope)
for (const { node, path } of utils.iterateReferencesTraceMap(tracker, {
ref: {
[ReferenceTracker.CALL]: true
},
computed: {
[ReferenceTracker.CALL]: true
},
toRef: {
[ReferenceTracker.CALL]: true
},
customRef: {
[ReferenceTracker.CALL]: true
},
shallowRef: {
[ReferenceTracker.CALL]: true
},
toRefs: {
[ReferenceTracker.CALL]: true
}
})) {
const expr = /** @type {CallExpression} */ (node)
yield {
node: expr,
name: path[path.length - 1]
}
}
}
/**
* Iterate the call expressions that defineModel() macro.
* @param {import('eslint').Scope.Scope} globalScope
* @returns {Iterable<{ node: CallExpression }>}
*/
function* iterateDefineModels(globalScope) {
for (const { identifier } of iterateMacroReferences()) {
if (
identifier.parent.type === 'CallExpression' &&
identifier.parent.callee === identifier
) {
yield {
node: identifier.parent
}
}
}
/**
* Iterate macro reference.
* @returns {Iterable<Reference>}
*/
function* iterateMacroReferences() {
const variable = globalScope.set.get('defineModel')
if (
variable &&
variable.defs.length === 0 /* It was automatically defined. */
) {
yield* variable.references
}
for (const ref of globalScope.through) {
if (ref.identifier.name === 'defineModel') {
yield ref
}
}
}
}
/**
* Iterate the call expressions that define the reactive variables.
* @param {import('eslint').Scope.Scope} globalScope
* @returns {Iterable<{ node: CallExpression, name: string }>}
*/
function* iterateDefineReactiveVariables(globalScope) {
for (const { identifier } of iterateRefMacroReferences()) {
if (
identifier.parent.type === 'CallExpression' &&
identifier.parent.callee === identifier
) {
yield {
node: identifier.parent,
name: identifier.name
}
}
}
/**
* Iterate ref macro reference.
* @returns {Iterable<Reference>}
*/
function* iterateRefMacroReferences() {
yield* REF_MACROS.map((m) => globalScope.set.get(m))
.filter(utils.isDef)
.flatMap((v) => v.references)
for (const ref of globalScope.through) {
if (REF_MACROS.includes(ref.identifier.name)) {
yield ref
}
}
}
}
/**
* Iterate the call expressions that the escape hint values.
* @param {import('eslint').Scope.Scope} globalScope
* @returns {Iterable<CallExpression>}
*/
function* iterateEscapeHintValueRefs(globalScope) {
for (const { identifier } of iterateEscapeHintReferences()) {
if (
identifier.parent.type === 'CallExpression' &&
identifier.parent.callee === identifier
) {
yield identifier.parent
}
}
/**
* Iterate escape hint reference.
* @returns {Iterable<Reference>}
*/
function* iterateEscapeHintReferences() {
const escapeHint = globalScope.set.get('$$')
if (escapeHint) {
yield* escapeHint.references
}
for (const ref of globalScope.through) {
if (ref.identifier.name === '$$') {
yield ref
}
}
}
}
/**
* Extract identifier from given pattern node.
* @param {Pattern} node
* @returns {Iterable<Identifier>}
*/
function* extractIdentifier(node) {
switch (node.type) {
case 'Identifier': {
yield node
break
}
case 'ObjectPattern': {
for (const property of node.properties) {
if (property.type === 'Property') {
yield* extractIdentifier(property.value)
} else if (property.type === 'RestElement') {
yield* extractIdentifier(property)
}
}
break
}
case 'ArrayPattern': {
for (const element of node.elements) {
if (element) {
yield* extractIdentifier(element)
}
}
break
}
case 'AssignmentPattern': {
yield* extractIdentifier(node.left)
break
}
case 'RestElement': {
yield* extractIdentifier(node.argument)
break
}
case 'MemberExpression': {
// can't extract
break
}
// No default
}
}
/**
* Iterate references of the given identifier.
* @param {Identifier} id
* @param {import('eslint').Scope.Scope} globalScope
* @returns {Iterable<import('eslint').Scope.Reference>}
*/
function* iterateIdentifierReferences(id, globalScope) {
const variable = eslintUtils.findVariable(globalScope, id)
if (!variable) {
return
}
for (const reference of variable.references) {
yield reference
}
}
/**
* @param {RuleContext} context The rule context.
*/
function getGlobalScope(context) {
const sourceCode = context.getSourceCode()
return (
sourceCode.scopeManager.globalScope || sourceCode.scopeManager.scopes[0]
)
}
module.exports = {
iterateDefineRefs,
extractRefObjectReferences,
extractReactiveVariableReferences
}
/**
* @typedef {object} RefObjectReferenceContext
* @property {string} method
* @property {CallExpression} define
* @property {(CallExpression | Identifier | MemberExpression)[]} defineChain Holds the initialization path for assignment of ref objects.
*/
/**
* @implements {RefObjectReferences}
*/
class RefObjectReferenceExtractor {
/**
* @param {RuleContext} context The rule context.
*/
constructor(context) {
this.context = context
/** @type {Map<Identifier | MemberExpression | CallExpression | ObjectPattern, RefObjectReference>} */
this.references = new Map()
/** @type {Set<Identifier>} */
this._processedIds = new Set()
}
/**
* @template {Identifier | Expression | Pattern | Super} T
* @param {T} node
* @returns {T extends Identifier ?
* RefObjectReferenceForIdentifier | null :
* T extends Expression ?
* RefObjectReferenceForExpression | null :
* T extends Pattern ?
* RefObjectReferenceForPattern | null :
* null}
*/
get(node) {
return /** @type {never} */ (
this.references.get(/** @type {never} */ (node)) || null
)
}
/**
* @param {CallExpression} node
* @param {string} method
*/
processDefineRef(node, method) {
const parent = node.parent
/** @type {Pattern | null} */
let pattern = null
if (parent.type === 'VariableDeclarator') {
pattern = parent.id
} else if (
parent.type === 'AssignmentExpression' &&
parent.operator === '='
) {
pattern = parent.left
} else {
if (method !== 'toRefs') {
this.references.set(node, {
type: 'expression',
node,
method,
define: node,
defineChain: [node]
})
}
return
}
const ctx = {
method,
define: node,
defineChain: [node]
}
if (method === 'toRefs') {
const propertyReferenceExtractor = definePropertyReferenceExtractor(
this.context
)
const propertyReferences =
propertyReferenceExtractor.extractFromPattern(pattern)
for (const name of propertyReferences.allProperties().keys()) {
for (const nest of propertyReferences.getNestNodes(name)) {
if (nest.type === 'expression') {
this.processMemberExpression(nest.node, ctx)
} else if (nest.type === 'pattern') {
this.processPattern(nest.node, ctx)
}
}
}
} else {
this.processPattern(pattern, ctx)
}
}
/**
* @param {CallExpression} node
*/
processDefineModel(node) {
const parent = node.parent
/** @type {Pattern | null} */
let pattern = null
if (parent.type === 'VariableDeclarator') {
pattern = parent.id
} else if (
parent.type === 'AssignmentExpression' &&
parent.operator === '='
) {
pattern = parent.left
} else {
return
}
const ctx = {
method: 'defineModel',
define: node,
defineChain: [node]
}
if (pattern.type === 'ArrayPattern' && pattern.elements[0]) {
pattern = pattern.elements[0]
}
this.processPattern(pattern, ctx)
}
/**
* @param {MemberExpression | Identifier} node
* @param {RefObjectReferenceContext} ctx
*/
processExpression(node, ctx) {
const parent = node.parent
if (parent.type === 'AssignmentExpression') {
if (parent.operator === '=' && parent.right === node) {
// `(foo = obj.mem)`
this.processPattern(parent.left, {
...ctx,
defineChain: [node, ...ctx.defineChain]
})
return true
}
} else if (parent.type === 'VariableDeclarator' && parent.init === node) {
// `const foo = obj.mem`
this.processPattern(parent.id, {
...ctx,
defineChain: [node, ...ctx.defineChain]
})
return true
}
return false
}
/**
* @param {MemberExpression} node
* @param {RefObjectReferenceContext} ctx
*/
processMemberExpression(node, ctx) {
if (this.processExpression(node, ctx)) {
return
}
this.references.set(node, {
type: 'expression',
node,
...ctx
})
}
/**
* @param {Pattern} node
* @param {RefObjectReferenceContext} ctx
*/
processPattern(node, ctx) {
switch (node.type) {
case 'Identifier': {
this.processIdentifierPattern(node, ctx)
break
}
case 'ArrayPattern':
case 'RestElement':
case 'MemberExpression': {
return
}
case 'ObjectPattern': {
this.references.set(node, {
type: 'pattern',
node,
...ctx
})
return
}
case 'AssignmentPattern': {
this.processPattern(node.left, ctx)
return
}
// No default
}
}
/**
* @param {Identifier} node
* @param {RefObjectReferenceContext} ctx
*/
processIdentifierPattern(node, ctx) {
if (this._processedIds.has(node)) {
return
}
this._processedIds.add(node)
for (const reference of iterateIdentifierReferences(
node,
getGlobalScope(this.context)
)) {
const def =
reference.resolved &&
reference.resolved.defs.length === 1 &&
reference.resolved.defs[0].type === 'Variable'
? reference.resolved.defs[0]
: null
if (def && def.name === reference.identifier) {
continue
}
if (
reference.isRead() &&
this.processExpression(reference.identifier, ctx)
) {
continue
}
this.references.set(reference.identifier, {
type: reference.isWrite() ? 'pattern' : 'expression',
node: reference.identifier,
variableDeclarator: def ? def.node : null,
variableDeclaration: def ? def.parent : null,
...ctx
})
}
}
}
/**
* Extracts references of all ref objects.
* @param {RuleContext} context The rule context.
* @returns {RefObjectReferences}
*/
function extractRefObjectReferences(context) {
const sourceCode = context.getSourceCode()
const cachedReferences = cacheForRefObjectReferences.get(sourceCode.ast)
if (cachedReferences) {
return cachedReferences
}
const references = new RefObjectReferenceExtractor(context)
const globalScope = getGlobalScope(context)
for (const { node, name } of iterateDefineRefs(globalScope)) {
references.processDefineRef(node, name)
}
for (const { node } of iterateDefineModels(globalScope)) {
references.processDefineModel(node)
}
cacheForRefObjectReferences.set(sourceCode.ast, references)
return references
}
/**
* @implements {ReactiveVariableReferences}
*/
class ReactiveVariableReferenceExtractor {
/**
* @param {RuleContext} context The rule context.
*/
constructor(context) {
this.context = context
/** @type {Map<Identifier, ReactiveVariableReference>} */
this.references = new Map()
/** @type {Set<Identifier>} */
this._processedIds = new Set()
/** @type {Set<CallExpression>} */
this._escapeHintValueRefs = new Set(
iterateEscapeHintValueRefs(getGlobalScope(context))
)
}
/**
* @param {Identifier} node
* @returns {ReactiveVariableReference | null}
*/
get(node) {
return this.references.get(node) || null
}
/**
* @param {CallExpression} node
* @param {string} method
*/
processDefineReactiveVariable(node, method) {
const parent = node.parent
if (parent.type !== 'VariableDeclarator') {
return
}
/** @type {Pattern | null} */
const pattern = parent.id
if (method === '$') {
for (const id of extractIdentifier(pattern)) {
this.processIdentifierPattern(id, method, node)
}
} else {
if (pattern.type === 'Identifier') {
this.processIdentifierPattern(pattern, method, node)
}
}
}
/**
* @param {Identifier} node
* @param {string} method
* @param {CallExpression} define
*/
processIdentifierPattern(node, method, define) {
if (this._processedIds.has(node)) {
return
}
this._processedIds.add(node)
for (const reference of iterateIdentifierReferences(
node,
getGlobalScope(this.context)
)) {
const def =
reference.resolved &&
reference.resolved.defs.length === 1 &&
reference.resolved.defs[0].type === 'Variable'
? reference.resolved.defs[0]
: null
if (!def || def.name === reference.identifier) {
continue
}
this.references.set(reference.identifier, {
node: reference.identifier,
escape: this.withinEscapeHint(reference.identifier),
method,
define,
variableDeclaration: def.parent
})
}
}
/**
* Checks whether the given identifier node within the escape hints (`$$()`) or not.
* @param {Identifier} node
*/
withinEscapeHint(node) {
/** @type {Identifier | ObjectExpression | ArrayExpression | SpreadElement | Property | AssignmentProperty} */
let target = node
/** @type {ASTNode | null} */
let parent = target.parent
while (parent) {
if (parent.type === 'CallExpression') {
if (
parent.arguments.includes(/** @type {any} */ (target)) &&
this._escapeHintValueRefs.has(parent)
) {
return true
}
return false
}
if (
(parent.type === 'Property' && parent.value === target) ||
(parent.type === 'ObjectExpression' &&
parent.properties.includes(/** @type {any} */ (target))) ||
parent.type === 'ArrayExpression' ||
parent.type === 'SpreadElement'
) {
target = parent
parent = target.parent
} else {
return false
}
}
return false
}
}
/**
* Extracts references of all reactive variables.
* @param {RuleContext} context The rule context.
* @returns {ReactiveVariableReferences}
*/
function extractReactiveVariableReferences(context) {
const sourceCode = context.getSourceCode()
const cachedReferences = cacheForReactiveVariableReferences.get(
sourceCode.ast
)
if (cachedReferences) {
return cachedReferences
}
const references = new ReactiveVariableReferenceExtractor(context)
for (const { node, name } of iterateDefineReactiveVariables(
getGlobalScope(context)
)) {
references.processDefineReactiveVariable(node, name)
}
cacheForReactiveVariableReferences.set(sourceCode.ast, references)
return references
}

View File

@@ -0,0 +1,83 @@
const RE_REGEXP_CHAR = /[\\^$.*+?()[\]{}|]/gu
const RE_HAS_REGEXP_CHAR = new RegExp(RE_REGEXP_CHAR.source)
const RE_REGEXP_STR = /^\/(.+)\/(.*)$/u
/**
* Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+",
* "?", "(", ")", "[", "]", "{", "}", and "|" in `string`.
*
* @param {string} string The string to escape.
* @returns {string} Returns the escaped string.
*/
function escape(string) {
return string && RE_HAS_REGEXP_CHAR.test(string)
? string.replace(RE_REGEXP_CHAR, String.raw`\$&`)
: string
}
/**
* Convert a string to the `RegExp`.
* Normal strings (e.g. `"foo"`) is converted to `/^foo$/` of `RegExp`.
* Strings like `"/^foo/i"` are converted to `/^foo/i` of `RegExp`.
*
* @param {string} string The string to convert.
* @param {{add?: string, remove?: string}} [flags] The flags to add or remove.
* - `add`: Flags to add to the `RegExp` (e.g. `'i'` for case-insensitive).
* - `remove`: Flags to remove from the `RegExp` (e.g. `'g'` to remove global matching).
* @returns {RegExp} Returns the `RegExp`.
*/
function toRegExp(string, flags = {}) {
const parts = RE_REGEXP_STR.exec(string)
const { add: forceAddFlags = '', remove: forceRemoveFlags = '' } =
typeof flags === 'object' ? flags : {} // Avoid issues when this is called directly from array.map
if (parts) {
return new RegExp(
parts[1],
parts[2].replace(
new RegExp(`[${forceAddFlags}${forceRemoveFlags}]`, 'g'),
''
) + forceAddFlags
)
}
return new RegExp(`^${escape(string)}$`, forceAddFlags)
}
/**
* Checks whether given string is regexp string
* @param {string} string
* @returns {boolean}
*/
function isRegExp(string) {
return RE_REGEXP_STR.test(string)
}
/**
* Converts an array of strings to a singular function to match any of them.
* This function converts each string to a `RegExp` and returns a function that checks all of them.
*
* @param {string[]} [patterns] The strings or regular expression strings to match.
* @returns {(...toCheck: string[]) => boolean} Returns a function that checks if any string matches any of the given patterns.
*/
function toRegExpGroupMatcher(patterns = []) {
if (patterns.length === 0) {
return () => false
}
// In the future, we could optimize this by joining expressions with identical flags.
const regexps = patterns.map((pattern) => toRegExp(pattern, { remove: 'g' }))
if (regexps.length === 1) {
return (...toCheck) => toCheck.some((str) => regexps[0].test(str))
}
return (...toCheck) =>
regexps.some((regexp) => toCheck.some((str) => regexp.test(str)))
}
module.exports = {
escape,
toRegExp,
isRegExp,
toRegExpGroupMatcher
}

View File

@@ -0,0 +1,30 @@
module.exports = {
getScope
}
/**
* Gets the scope for the current node
* @param {RuleContext} context The rule context
* @param {ESNode} currentNode The node to get the scope of
* @returns { import('eslint').Scope.Scope } The scope information for this node
*/
function getScope(context, currentNode) {
// On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope.
const inner = currentNode.type !== 'Program'
const scopeManager = context.getSourceCode().scopeManager
/** @type {ESNode | null} */
let node = currentNode
for (; node; node = /** @type {ESNode | null} */ (node.parent)) {
const scope = scopeManager.acquire(node, inner)
if (scope) {
if (scope.type === 'function-expression-name') {
return scope.childScopes[0]
}
return scope
}
}
return scopeManager.scopes[0]
}

View File

@@ -0,0 +1,635 @@
'use strict'
const parser = require('postcss-selector-parser')
const { default: nthCheck } = require('nth-check')
const { getAttribute, isVElement } = require('.')
/**
* @typedef {object} VElementSelector
* @property {(element: VElement)=>boolean} test
*/
module.exports = {
parseSelector
}
/**
* Parses CSS selectors and returns an object with a function that tests VElement.
* @param {string} selector CSS selector
* @param {RuleContext} context - The rule context.
* @returns {VElementSelector}
*/
function parseSelector(selector, context) {
let astSelector
try {
astSelector = parser().astSync(selector)
} catch {
context.report({
loc: { line: 0, column: 0 },
message: `Cannot parse selector: ${selector}.`
})
return {
test: () => false
}
}
try {
const test = selectorsToVElementMatcher(astSelector.nodes)
return {
test(element) {
return test(element, null)
}
}
} catch (error) {
if (error instanceof SelectorError) {
context.report({
loc: { line: 0, column: 0 },
message: error.message
})
return {
test: () => false
}
}
throw error
}
}
class SelectorError extends Error {}
/**
* @typedef {(element: VElement, subject: VElement | null )=>boolean} VElementMatcher
* @typedef {Exclude<parser.Selector['nodes'][number], {type:'comment'|'root'}>} ChildNode
*/
/**
* Convert nodes to VElementMatcher
* @param {parser.Selector[]} selectorNodes
* @returns {VElementMatcher}
*/
function selectorsToVElementMatcher(selectorNodes) {
const selectors = selectorNodes.map((n) =>
selectorToVElementMatcher(cleanSelectorChildren(n))
)
return (element, subject) => selectors.some((sel) => sel(element, subject))
}
/**
* @param {parser.Node|null} node
* @returns {node is parser.Combinator}
*/
function isDescendantCombinator(node) {
return Boolean(node && node.type === 'combinator' && !node.value.trim())
}
/**
* Clean and get the selector child nodes.
* @param {parser.Selector} selector
* @returns {ChildNode[]}
*/
function cleanSelectorChildren(selector) {
/** @type {ChildNode[]} */
const nodes = []
/** @type {ChildNode|null} */
let last = null
for (const node of selector.nodes) {
if (node.type === 'root') {
throw new SelectorError('Unexpected state type=root')
}
if (node.type === 'comment') {
continue
}
if (
(last == null || last.type === 'combinator') &&
isDescendantCombinator(node)
) {
// Ignore descendant combinator
continue
}
if (isDescendantCombinator(last) && node.type === 'combinator') {
// Replace combinator
nodes.pop()
}
nodes.push(node)
last = node
}
if (isDescendantCombinator(last)) {
nodes.pop()
}
return nodes
}
/**
* Convert Selector child nodes to VElementMatcher
* @param {ChildNode[]} selectorChildren
* @returns {VElementMatcher}
*/
function selectorToVElementMatcher(selectorChildren) {
const nodes = [...selectorChildren]
let node = nodes.shift()
/**
* @type {VElementMatcher | null}
*/
let result = null
while (node) {
if (node.type === 'combinator') {
const combinator = node.value
node = nodes.shift()
if (!node) {
throw new SelectorError(`Expected selector after '${combinator}'.`)
}
if (node.type === 'combinator') {
throw new SelectorError(`Unexpected combinator '${node.value}'.`)
}
const right = nodeToVElementMatcher(node)
result = combination(
result ||
// for :has()
((element, subject) => element === subject),
combinator,
right
)
} else {
const sel = nodeToVElementMatcher(node)
result = result ? compound(result, sel) : sel
}
node = nodes.shift()
}
if (!result) {
throw new SelectorError(`Unexpected empty selector.`)
}
return result
}
/**
* @param {VElementMatcher} left
* @param {string} combinator
* @param {VElementMatcher} right
* @returns {VElementMatcher}
*/
function combination(left, combinator, right) {
switch (combinator.trim()) {
case '': {
// descendant
return (element, subject) => {
if (right(element, null)) {
let parent = element.parent
while (parent.type === 'VElement') {
if (left(parent, subject)) {
return true
}
parent = parent.parent
}
}
return false
}
}
case '>': {
// child
return (element, subject) => {
if (right(element, null)) {
const parent = element.parent
if (parent.type === 'VElement') {
return left(parent, subject)
}
}
return false
}
}
case '+': {
// adjacent
return (element, subject) => {
if (right(element, null)) {
const before = getBeforeElement(element)
if (before) {
return left(before, subject)
}
}
return false
}
}
case '~': {
// sibling
return (element, subject) => {
if (right(element, null)) {
for (const before of getBeforeElements(element)) {
if (left(before, subject)) {
return true
}
}
}
return false
}
}
default: {
throw new SelectorError(`Unknown combinator: ${combinator}.`)
}
}
}
/**
* Convert node to VElementMatcher
* @param {Exclude<parser.Node, {type:'combinator'|'comment'|'root'|'selector'}>} selector
* @returns {VElementMatcher}
*/
function nodeToVElementMatcher(selector) {
switch (selector.type) {
case 'attribute': {
return attributeNodeToVElementMatcher(selector)
}
case 'class': {
return classNameNodeToVElementMatcher(selector)
}
case 'id': {
return identifierNodeToVElementMatcher(selector)
}
case 'tag': {
return tagNodeToVElementMatcher(selector)
}
case 'universal': {
return universalNodeToVElementMatcher(selector)
}
case 'pseudo': {
return pseudoNodeToVElementMatcher(selector)
}
case 'nesting': {
throw new SelectorError('Unsupported nesting selector.')
}
case 'string': {
throw new SelectorError(`Unknown selector: ${selector.value}.`)
}
default: {
throw new SelectorError(
`Unknown selector: ${/** @type {any}*/ (selector).value}.`
)
}
}
}
/**
* Convert Attribute node to VElementMatcher
* @param {parser.Attribute} selector
* @returns {VElementMatcher}
*/
function attributeNodeToVElementMatcher(selector) {
const key = selector.attribute
if (!selector.operator) {
return (element) => getAttributeValue(element, key) != null
}
const value = selector.value || ''
switch (selector.operator) {
case '=': {
return buildVElementMatcher(value, (attr, val) => attr === val)
}
case '~=': {
// words
return buildVElementMatcher(value, (attr, val) =>
attr.split(/\s+/gu).includes(val)
)
}
case '|=': {
// immediately followed by hyphen
return buildVElementMatcher(
value,
(attr, val) => attr === val || attr.startsWith(`${val}-`)
)
}
case '^=': {
// prefixed
return buildVElementMatcher(value, (attr, val) => attr.startsWith(val))
}
case '$=': {
// suffixed
return buildVElementMatcher(value, (attr, val) => attr.endsWith(val))
}
case '*=': {
// contains
return buildVElementMatcher(value, (attr, val) => attr.includes(val))
}
default: {
throw new SelectorError(`Unsupported operator: ${selector.operator}.`)
}
}
/**
* @param {string} selectorValue
* @param {(attrValue:string, selectorValue: string)=>boolean} test
* @returns {VElementMatcher}
*/
function buildVElementMatcher(selectorValue, test) {
const val = selector.insensitive
? selectorValue.toLowerCase()
: selectorValue
return (element) => {
const attrValue = getAttributeValue(element, key)
if (attrValue == null) {
return false
}
return test(
selector.insensitive ? attrValue.toLowerCase() : attrValue,
val
)
}
}
}
/**
* Convert ClassName node to VElementMatcher
* @param {parser.ClassName} selector
* @returns {VElementMatcher}
*/
function classNameNodeToVElementMatcher(selector) {
const className = selector.value
return (element) => {
const attrValue = getAttributeValue(element, 'class')
if (attrValue == null) {
return false
}
return attrValue.split(/\s+/gu).includes(className)
}
}
/**
* Convert Identifier node to VElementMatcher
* @param {parser.Identifier} selector
* @returns {VElementMatcher}
*/
function identifierNodeToVElementMatcher(selector) {
const id = selector.value
return (element) => {
const attrValue = getAttributeValue(element, 'id')
if (attrValue == null) {
return false
}
return attrValue === id
}
}
/**
* Convert Tag node to VElementMatcher
* @param {parser.Tag} selector
* @returns {VElementMatcher}
*/
function tagNodeToVElementMatcher(selector) {
const name = selector.value
return (element) => element.rawName === name
}
/**
* Convert Universal node to VElementMatcher
* @param {parser.Universal} _selector
* @returns {VElementMatcher}
*/
function universalNodeToVElementMatcher(_selector) {
return () => true
}
/**
* Convert Pseudo node to VElementMatcher
* @param {parser.Pseudo} selector
* @returns {VElementMatcher}
*/
function pseudoNodeToVElementMatcher(selector) {
const pseudo = selector.value
switch (pseudo) {
case ':not': {
// https://developer.mozilla.org/en-US/docs/Web/CSS/:not
const selectors = selectorsToVElementMatcher(selector.nodes)
return (element, subject) => !selectors(element, subject)
}
case ':is':
case ':where': {
// https://developer.mozilla.org/en-US/docs/Web/CSS/:is
// https://developer.mozilla.org/en-US/docs/Web/CSS/:where
return selectorsToVElementMatcher(selector.nodes)
}
case ':has': {
// https://developer.mozilla.org/en-US/docs/Web/CSS/:has
return pseudoHasSelectorsToVElementMatcher(selector.nodes)
}
case ':empty': {
// https://developer.mozilla.org/en-US/docs/Web/CSS/:empty
return (element) =>
element.children.every(
(child) => child.type === 'VText' && !child.value.trim()
)
}
case ':nth-child': {
// https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child
const nth = parseNth(selector)
return buildPseudoNthVElementMatcher(nth)
}
case ':nth-last-child': {
// https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-last-child
const nth = parseNth(selector)
return buildPseudoNthVElementMatcher((index, length) =>
nth(length - index - 1)
)
}
case ':first-child': {
// https://developer.mozilla.org/en-US/docs/Web/CSS/:first-child
return buildPseudoNthVElementMatcher((index) => index === 0)
}
case ':last-child': {
// https://developer.mozilla.org/en-US/docs/Web/CSS/:last-child
return buildPseudoNthVElementMatcher(
(index, length) => index === length - 1
)
}
case ':only-child': {
// https://developer.mozilla.org/en-US/docs/Web/CSS/:only-child
return buildPseudoNthVElementMatcher(
(index, length) => index === 0 && length === 1
)
}
case ':nth-of-type': {
// https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-of-type
const nth = parseNth(selector)
return buildPseudoNthOfTypeVElementMatcher(nth)
}
case ':nth-last-of-type': {
// https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-last-of-type
const nth = parseNth(selector)
return buildPseudoNthOfTypeVElementMatcher((index, length) =>
nth(length - index - 1)
)
}
case ':first-of-type': {
// https://developer.mozilla.org/en-US/docs/Web/CSS/:first-of-type
return buildPseudoNthOfTypeVElementMatcher((index) => index === 0)
}
case ':last-of-type': {
// https://developer.mozilla.org/en-US/docs/Web/CSS/:last-of-type
return buildPseudoNthOfTypeVElementMatcher(
(index, length) => index === length - 1
)
}
case ':only-of-type': {
// https://developer.mozilla.org/en-US/docs/Web/CSS/:only-of-type
return buildPseudoNthOfTypeVElementMatcher(
(index, length) => index === 0 && length === 1
)
}
default: {
throw new SelectorError(`Unsupported pseudo selector: ${pseudo}.`)
}
}
}
/**
* Convert :has() selector nodes to VElementMatcher
* @param {parser.Selector[]} selectorNodes
* @returns {VElementMatcher}
*/
function pseudoHasSelectorsToVElementMatcher(selectorNodes) {
const selectors = selectorNodes.map((n) =>
pseudoHasSelectorToVElementMatcher(n)
)
return (element, subject) => selectors.some((sel) => sel(element, subject))
}
/**
* Convert :has() selector node to VElementMatcher
* @param {parser.Selector} selector
* @returns {VElementMatcher}
*/
function pseudoHasSelectorToVElementMatcher(selector) {
const nodes = cleanSelectorChildren(selector)
const selectors = selectorToVElementMatcher(nodes)
const firstNode = nodes[0]
if (
firstNode.type === 'combinator' &&
(firstNode.value === '+' || firstNode.value === '~')
) {
// adjacent or sibling
return buildVElementMatcher(selectors, (element) =>
getAfterElements(element)
)
}
// descendant or child
return buildVElementMatcher(selectors, (element) =>
element.children.filter(isVElement)
)
}
/**
* @param {VElementMatcher} selectors
* @param {(element: VElement) => VElement[]} getStartElements
* @returns {VElementMatcher}
*/
function buildVElementMatcher(selectors, getStartElements) {
return (element) => {
const elements = [...getStartElements(element)]
/** @type {VElement|undefined} */
let curr
while ((curr = elements.shift())) {
const el = curr
if (selectors(el, element)) {
return true
}
elements.push(...el.children.filter(isVElement))
}
return false
}
}
/**
* Parse <nth>
* @param {parser.Pseudo} pseudoNode
* @returns {(index: number)=>boolean}
*/
function parseNth(pseudoNode) {
const argumentsText = pseudoNode
.toString()
.slice(pseudoNode.value.length)
.toLowerCase()
const openParenIndex = argumentsText.indexOf('(')
const closeParenIndex = argumentsText.lastIndexOf(')')
if (openParenIndex === -1 || closeParenIndex === -1) {
throw new SelectorError(
`Cannot parse An+B micro syntax (:nth-xxx() argument): ${argumentsText}.`
)
}
const argument = argumentsText
.slice(openParenIndex + 1, closeParenIndex)
.trim()
try {
return nthCheck(argument)
} catch {
throw new SelectorError(
`Cannot parse An+B micro syntax (:nth-xxx() argument): '${argument}'.`
)
}
}
/**
* Build VElementMatcher for :nth-xxx()
* @param {(index: number, length: number)=>boolean} testIndex
* @returns {VElementMatcher}
*/
function buildPseudoNthVElementMatcher(testIndex) {
return (element) => {
const elements = element.parent.children.filter(isVElement)
return testIndex(elements.indexOf(element), elements.length)
}
}
/**
* Build VElementMatcher for :nth-xxx-of-type()
* @param {(index: number, length: number)=>boolean} testIndex
* @returns {VElementMatcher}
*/
function buildPseudoNthOfTypeVElementMatcher(testIndex) {
return (element) => {
const elements = element.parent.children.filter(
/** @returns {e is VElement} */
(e) => isVElement(e) && e.rawName === element.rawName
)
return testIndex(elements.indexOf(element), elements.length)
}
}
/**
* @param {VElement} element
*/
function getBeforeElement(element) {
return getBeforeElements(element).pop() || null
}
/**
* @param {VElement} element
*/
function getBeforeElements(element) {
const parent = element.parent
const index = parent.children.indexOf(element)
return parent.children.slice(0, index).filter(isVElement)
}
/**
* @param {VElement} element
*/
function getAfterElements(element) {
const parent = element.parent
const index = parent.children.indexOf(element)
return parent.children.slice(index + 1).filter(isVElement)
}
/**
* @param {VElementMatcher} a
* @param {VElementMatcher} b
* @returns {VElementMatcher}
*/
function compound(a, b) {
return (element, subject) => a(element, subject) && b(element, subject)
}
/**
* Get attribute value from given element.
* @param {VElement} element The element node.
* @param {string} attribute The attribute name.
*/
function getAttributeValue(element, attribute) {
const attr = getAttribute(element, attribute)
if (attr) {
return (attr.value && attr.value.value) || ''
}
return null
}

View File

@@ -0,0 +1,64 @@
const { isVElement } = require('..')
class StyleVariablesContext {
/**
* @param {RuleContext} context
* @param {VElement[]} styles
*/
constructor(context, styles) {
this.context = context
this.styles = styles
/** @type {VReference[]} */
this.references = []
/** @type {VExpressionContainer[]} */
this.vBinds = []
for (const style of styles) {
for (const node of style.children) {
if (node.type === 'VExpressionContainer') {
this.vBinds.push(node)
for (const ref of node.references.filter(
(ref) => ref.variable == null
)) {
this.references.push(ref)
}
}
}
}
}
}
module.exports = {
getStyleVariablesContext,
StyleVariablesContext
}
/** @type {WeakMap<VElement, StyleVariablesContext>} */
const cache = new WeakMap()
/**
* Get the style vars context
* @param {RuleContext} context
* @returns {StyleVariablesContext | null}
*/
function getStyleVariablesContext(context) {
const sourceCode = context.getSourceCode()
const df =
sourceCode.parserServices.getDocumentFragment &&
sourceCode.parserServices.getDocumentFragment()
if (!df) {
return null
}
const styles = df.children.filter(
/** @returns {e is VElement} */
(e) => isVElement(e) && e.name === 'style'
)
if (styles.length === 0) {
return null
}
let ctx = cache.get(styles[0])
if (ctx) {
return ctx
}
ctx = new StyleVariablesContext(context, styles)
cache.set(styles[0], ctx)
return ctx
}

View File

@@ -0,0 +1,138 @@
[
"accent-height",
"alignment-baseline",
"arabic-form",
"attributeName",
"attributeType",
"baseFrequency",
"baseline-shift",
"baseProfile",
"calcMode",
"cap-height",
"clipPathUnits",
"clip-path",
"clip-rule",
"color-interpolation",
"color-interpolation-filters",
"color-profile",
"color-rendering",
"contentScriptType",
"contentStyleType",
"diffuseConstant",
"dominant-baseline",
"edgeMode",
"enable-background",
"externalResourcesRequired",
"fill-opacity",
"fill-rule",
"filterRes",
"filterUnits",
"flood-color",
"flood-opacity",
"font-family",
"font-size",
"font-size-adjust",
"font-stretch",
"font-style",
"font-variant",
"font-weight",
"glyph-name",
"glyph-orientation-horizontal",
"glyph-orientation-vertical",
"glyphRef",
"gradientTransform",
"gradientUnits",
"horiz-adv-x",
"horiz-origin-x",
"image-rendering",
"kernelMatrix",
"kernelUnitLength",
"keyPoints",
"keySplines",
"keyTimes",
"lengthAdjust",
"letter-spacing",
"lighting-color",
"limitingConeAngle",
"marker-end",
"marker-mid",
"marker-start",
"markerHeight",
"markerUnits",
"markerWidth",
"maskContentUnits",
"maskUnits",
"numOctaves",
"overline-position",
"overline-thickness",
"panose-1",
"paint-order",
"pathLength",
"patternContentUnits",
"patternTransform",
"patternUnits",
"pointer-events",
"pointsAtX",
"pointsAtY",
"pointsAtZ",
"preserveAlpha",
"preserveAspectRatio",
"primitiveUnits",
"referrerPolicy",
"refX",
"refY",
"rendering-intent",
"repeatCount",
"repeatDur",
"requiredExtensions",
"requiredFeatures",
"shape-rendering",
"specularConstant",
"specularExponent",
"spreadMethod",
"startOffset",
"stdDeviation",
"stitchTiles",
"stop-color",
"stop-opacity",
"strikethrough-position",
"strikethrough-thickness",
"stroke-dasharray",
"stroke-dashoffset",
"stroke-linecap",
"stroke-linejoin",
"stroke-miterlimit",
"stroke-opacity",
"stroke-width",
"surfaceScale",
"systemLanguage",
"tableValues",
"targetX",
"targetY",
"text-anchor",
"text-decoration",
"text-rendering",
"textLength",
"transform-origin",
"underline-position",
"underline-thickness",
"unicode-bidi",
"unicode-range",
"units-per-em",
"v-alphabetic",
"v-hanging",
"v-ideographic",
"v-mathematical",
"vector-effect",
"vert-adv-y",
"vert-origin-x",
"vert-origin-y",
"viewBox",
"viewTarget",
"word-spacing",
"writing-mode",
"x-height",
"xChannelSelector",
"yChannelSelector",
"zoomAndPan"
]

View File

@@ -0,0 +1,65 @@
[
"a",
"animate",
"animateMotion",
"animateTransform",
"circle",
"clipPath",
"defs",
"desc",
"ellipse",
"feBlend",
"feColorMatrix",
"feComponentTransfer",
"feComposite",
"feConvolveMatrix",
"feDiffuseLighting",
"feDisplacementMap",
"feDistantLight",
"feDropShadow",
"feFlood",
"feFuncA",
"feFuncB",
"feFuncG",
"feFuncR",
"feGaussianBlur",
"feImage",
"feMerge",
"feMergeNode",
"feMorphology",
"feOffset",
"fePointLight",
"feSpecularLighting",
"feSpotLight",
"feTile",
"feTurbulence",
"filter",
"foreignObject",
"g",
"image",
"line",
"linearGradient",
"marker",
"mask",
"metadata",
"mpath",
"path",
"pattern",
"polygon",
"polyline",
"radialGradient",
"rect",
"script",
"set",
"stop",
"style",
"svg",
"switch",
"symbol",
"text",
"textPath",
"title",
"tspan",
"use",
"view"
]

View File

@@ -0,0 +1,121 @@
const {
isTypeNode,
extractRuntimeProps,
isTSTypeLiteral,
isTSTypeLiteralOrTSFunctionType,
extractRuntimeEmits,
flattenTypeNodes,
isTSInterfaceBody,
extractRuntimeSlots
} = require('./ts-ast')
const {
getComponentPropsFromTypeDefineTypes,
getComponentEmitsFromTypeDefineTypes,
getComponentSlotsFromTypeDefineTypes
} = require('./ts-types')
/**
* @typedef {import('@typescript-eslint/types').TSESTree.TypeNode} TSESTreeTypeNode
*/
/**
* @typedef {import('../index').ComponentTypeProp} ComponentTypeProp
* @typedef {import('../index').ComponentInferTypeProp} ComponentInferTypeProp
* @typedef {import('../index').ComponentUnknownProp} ComponentUnknownProp
* @typedef {import('../index').ComponentTypeEmit} ComponentTypeEmit
* @typedef {import('../index').ComponentInferTypeEmit} ComponentInferTypeEmit
* @typedef {import('../index').ComponentUnknownEmit} ComponentUnknownEmit
* @typedef {import('../index').ComponentTypeSlot} ComponentTypeSlot
* @typedef {import('../index').ComponentInferTypeSlot} ComponentInferTypeSlot
* @typedef {import('../index').ComponentUnknownSlot} ComponentUnknownSlot
*/
module.exports = {
isTypeNode,
getComponentPropsFromTypeDefine,
getComponentEmitsFromTypeDefine,
getComponentSlotsFromTypeDefine
}
/**
* 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|ComponentInferTypeProp|ComponentUnknownProp)[]} Array of component props
*/
function getComponentPropsFromTypeDefine(context, propsNode) {
/** @type {(ComponentTypeProp|ComponentInferTypeProp|ComponentUnknownProp)[]} */
const result = []
for (const defNode of flattenTypeNodes(
context,
/** @type {TSESTreeTypeNode} */ (propsNode)
)) {
if (isTSInterfaceBody(defNode) || isTSTypeLiteral(defNode)) {
result.push(...extractRuntimeProps(context, defNode))
} else {
result.push(
...getComponentPropsFromTypeDefineTypes(
context,
/** @type {TypeNode} */ (defNode)
)
)
}
}
return result
}
/**
* 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|ComponentInferTypeEmit|ComponentUnknownEmit)[]} Array of component emits
*/
function getComponentEmitsFromTypeDefine(context, emitsNode) {
/** @type {(ComponentTypeEmit|ComponentInferTypeEmit|ComponentUnknownEmit)[]} */
const result = []
for (const defNode of flattenTypeNodes(
context,
/** @type {TSESTreeTypeNode} */ (emitsNode)
)) {
if (
isTSInterfaceBody(defNode) ||
isTSTypeLiteralOrTSFunctionType(defNode)
) {
result.push(...extractRuntimeEmits(defNode))
} else {
result.push(
...getComponentEmitsFromTypeDefineTypes(
context,
/** @type {TypeNode} */ (defNode)
)
)
}
}
return result
}
/**
* Get all slots by looking at all component's properties
* @param {RuleContext} context The ESLint rule context object.
* @param {TypeNode} slotsNode Type with slots definition
* @return {(ComponentTypeSlot|ComponentInferTypeSlot|ComponentUnknownSlot)[]} Array of component slots
*/
function getComponentSlotsFromTypeDefine(context, slotsNode) {
/** @type {(ComponentTypeSlot|ComponentInferTypeSlot|ComponentUnknownSlot)[]} */
const result = []
for (const defNode of flattenTypeNodes(
context,
/** @type {TSESTreeTypeNode} */ (slotsNode)
)) {
if (isTSInterfaceBody(defNode) || isTSTypeLiteral(defNode)) {
result.push(...extractRuntimeSlots(defNode))
} else {
result.push(
...getComponentSlotsFromTypeDefineTypes(
context,
/** @type {TypeNode} */ (defNode)
)
)
}
}
return result
}

View File

@@ -0,0 +1,603 @@
const { getScope } = require('../scope')
const { findVariable } = require('@eslint-community/eslint-utils')
const { inferRuntimeTypeFromTypeNode } = require('./ts-types')
/**
* @typedef {import('@typescript-eslint/types').TSESTree.TypeNode} TSESTreeTypeNode
* @typedef {import('@typescript-eslint/types').TSESTree.TSInterfaceBody} TSESTreeTSInterfaceBody
* @typedef {import('@typescript-eslint/types').TSESTree.TSTypeLiteral} TSESTreeTSTypeLiteral
* @typedef {import('@typescript-eslint/types').TSESTree.TSFunctionType} TSESTreeTSFunctionType
* @typedef {import('@typescript-eslint/types').TSESTree.Parameter} TSESTreeParameter
* @typedef {import('@typescript-eslint/types').TSESTree.Node} TSESTreeNode
*
*/
/**
* @typedef {import('../index').ComponentTypeProp} ComponentTypeProp
* @typedef {import('../index').ComponentUnknownProp} ComponentUnknownProp
* @typedef {import('../index').ComponentTypeEmit} ComponentTypeEmit
* @typedef {import('../index').ComponentUnknownEmit} ComponentUnknownEmit
* @typedef {import('../index').ComponentTypeSlot} ComponentTypeSlot
* @typedef {import('../index').ComponentUnknownSlot} ComponentUnknownSlot
*/
const noop = Function.prototype
module.exports = {
isTypeNode,
flattenTypeNodes,
isTSInterfaceBody,
isTSTypeLiteral,
isTSTypeLiteralOrTSFunctionType,
extractRuntimeProps,
extractRuntimeEmits,
extractRuntimeSlots
}
/**
* @param {ASTNode} node
* @returns {node is TypeNode}
*/
function isTypeNode(node) {
if (
node.type === 'TSAbstractKeyword' ||
node.type === 'TSAnyKeyword' ||
node.type === 'TSAsyncKeyword' ||
node.type === 'TSArrayType' ||
node.type === 'TSBigIntKeyword' ||
node.type === 'TSBooleanKeyword' ||
node.type === 'TSConditionalType' ||
node.type === 'TSConstructorType' ||
node.type === 'TSDeclareKeyword' ||
node.type === 'TSExportKeyword' ||
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 === 'TSQualifiedName' ||
node.type === 'TSPrivateKeyword' ||
node.type === 'TSProtectedKeyword' ||
node.type === 'TSPublicKeyword' ||
node.type === 'TSReadonlyKeyword' ||
node.type === 'TSRestType' ||
node.type === 'TSStaticKeyword' ||
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'
) {
/** @type {TypeNode['type']} for type check */
const type = node.type
noop(type)
return true
}
/** @type {Exclude<ASTNode['type'], TypeNode['type']>} for type check */
const type = node.type
noop(type)
return false
}
/**
* @param {TSESTreeTypeNode|TSESTreeTSInterfaceBody} node
* @returns {node is TSESTreeTSInterfaceBody}
*/
function isTSInterfaceBody(node) {
return node.type === 'TSInterfaceBody'
}
/**
* @param {TSESTreeTypeNode} node
* @returns {node is TSESTreeTSTypeLiteral}
*/
function isTSTypeLiteral(node) {
return node.type === 'TSTypeLiteral'
}
/**
* @param {TSESTreeTypeNode} node
* @returns {node is TSESTreeTSFunctionType}
*/
function isTSFunctionType(node) {
return node.type === 'TSFunctionType'
}
/**
* @param {TSESTreeTypeNode} node
* @returns {node is TSESTreeTSTypeLiteral | TSESTreeTSFunctionType}
*/
function isTSTypeLiteralOrTSFunctionType(node) {
return isTSTypeLiteral(node) || isTSFunctionType(node)
}
/**
* @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 {TSESTreeTSTypeLiteral | TSESTreeTSInterfaceBody} node
* @returns {IterableIterator<ComponentTypeProp | ComponentUnknownProp>}
*/
function* extractRuntimeProps(context, node) {
const members = node.type === 'TSTypeLiteral' ? node.members : node.body
for (const member of members) {
if (
member.type === 'TSPropertySignature' ||
member.type === 'TSMethodSignature'
) {
if (member.key.type !== 'Identifier' && member.key.type !== 'Literal') {
yield {
type: 'unknown',
propName: null,
node: /** @type {Expression} */ (member.key)
}
continue
}
/** @type {string[]|undefined} */
let types
if (member.type === 'TSMethodSignature') {
types = ['Function']
} else if (member.typeAnnotation) {
types = inferRuntimeType(context, member.typeAnnotation.typeAnnotation)
}
yield {
type: 'type',
key: /** @type {Identifier | Literal} */ (member.key),
propName:
member.key.type === 'Identifier'
? member.key.name
: `${member.key.value}`,
node: /** @type {TSPropertySignature | TSMethodSignature} */ (member),
required: !member.optional,
types: types || [`null`]
}
}
}
}
/**
* @param {TSESTreeTSTypeLiteral | TSESTreeTSInterfaceBody | TSESTreeTSFunctionType} node
* @returns {IterableIterator<ComponentTypeEmit | ComponentUnknownEmit>}
*/
function* extractRuntimeEmits(node) {
if (node.type === 'TSFunctionType') {
yield* extractEventNames(
node.params[0],
/** @type {TSFunctionType} */ (node)
)
return
}
const members = node.type === 'TSTypeLiteral' ? node.members : node.body
for (const member of members) {
if (member.type === 'TSCallSignatureDeclaration') {
yield* extractEventNames(
member.params[0],
/** @type {TSCallSignatureDeclaration} */ (member)
)
} else if (
member.type === 'TSPropertySignature' ||
member.type === 'TSMethodSignature'
) {
if (member.key.type !== 'Identifier' && member.key.type !== 'Literal') {
yield {
type: 'unknown',
emitName: null,
node: /** @type {Expression} */ (member.key)
}
continue
}
yield {
type: 'type',
key: /** @type {Identifier | Literal} */ (member.key),
emitName:
member.key.type === 'Identifier'
? member.key.name
: `${member.key.value}`,
node: /** @type {TSPropertySignature | TSMethodSignature} */ (member)
}
}
}
}
/**
* @param {TSESTreeTSTypeLiteral | TSESTreeTSInterfaceBody} node
* @returns {IterableIterator<ComponentTypeSlot | ComponentUnknownSlot>}
*/
function* extractRuntimeSlots(node) {
const members = node.type === 'TSTypeLiteral' ? node.members : node.body
for (const member of members) {
if (
member.type === 'TSPropertySignature' ||
member.type === 'TSMethodSignature'
) {
if (member.key.type !== 'Identifier' && member.key.type !== 'Literal') {
yield {
type: 'unknown',
slotName: null,
node: /** @type {Expression} */ (member.key)
}
continue
}
yield {
type: 'type',
key: /** @type {Identifier | Literal} */ (member.key),
slotName:
member.key.type === 'Identifier'
? member.key.name
: `${member.key.value}`,
node: /** @type {TSPropertySignature | TSMethodSignature} */ (member)
}
}
}
}
/**
* @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,
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,
node: member
}
}
}
}
}
}
/**
* @param {RuleContext} context The ESLint rule context object.
* @param {TSESTreeTypeNode} node
* @returns {(TSESTreeTypeNode|TSESTreeTSInterfaceBody)[]}
*/
function flattenTypeNodes(context, node) {
/**
* @typedef {object} TraversedData
* @property {Set<TSESTreeTypeNode|TSESTreeTSInterfaceBody>} nodes
* @property {boolean} finished
*/
/** @type {Map<TSESTreeTypeNode,TraversedData>} */
const traversed = new Map()
return [...flattenImpl(node)]
/**
* @param {TSESTreeTypeNode} node
* @returns {Iterable<TSESTreeTypeNode|TSESTreeTSInterfaceBody>}
*/
function* flattenImpl(node) {
if (node.type === 'TSUnionType' || node.type === 'TSIntersectionType') {
for (const typeNode of node.types) {
yield* flattenImpl(typeNode)
}
return
}
if (
node.type === 'TSTypeReference' &&
node.typeName.type === 'Identifier'
) {
const refName = node.typeName.name
const variable = findVariable(
getScope(context, /** @type {any} */ (node)),
refName
)
if (variable && variable.defs.length === 1) {
const defNode = /** @type {TSESTreeNode} */ (variable.defs[0].node)
if (defNode.type === 'TSInterfaceDeclaration') {
yield defNode.body
return
} else if (defNode.type === 'TSTypeAliasDeclaration') {
const typeAnnotation = defNode.typeAnnotation
let traversedData = traversed.get(typeAnnotation)
if (traversedData) {
const copy = [...traversedData.nodes]
yield* copy
if (!traversedData.finished) {
// Include the node because it will probably be referenced recursively.
yield typeAnnotation
}
return
}
traversedData = { nodes: new Set(), finished: false }
traversed.set(typeAnnotation, traversedData)
for (const e of flattenImpl(typeAnnotation)) {
traversedData.nodes.add(e)
}
traversedData.finished = true
yield* traversedData.nodes
return
}
}
}
yield node
}
}
/**
* @param {RuleContext} context The ESLint rule context object.
* @param {TSESTreeTypeNode} node
* @param {Set<TSESTreeTypeNode>} [checked]
* @returns {string[]}
*/
function inferRuntimeType(context, node, checked = new Set()) {
switch (node.type) {
case 'TSStringKeyword':
case 'TSTemplateLiteralType': {
return ['String']
}
case 'TSNumberKeyword': {
return ['Number']
}
case 'TSBooleanKeyword': {
return ['Boolean']
}
case 'TSObjectKeyword': {
return ['Object']
}
case 'TSTypeLiteral': {
return inferTypeLiteralType(node)
}
case 'TSFunctionType': {
return ['Function']
}
case 'TSArrayType':
case 'TSTupleType': {
return ['Array']
}
case 'TSSymbolKeyword': {
return ['Symbol']
}
case 'TSLiteralType': {
if (node.literal.type === 'Literal') {
switch (typeof node.literal.value) {
case 'boolean': {
return ['Boolean']
}
case 'string': {
return ['String']
}
case 'number':
case 'bigint': {
return ['Number']
}
}
if (node.literal.value instanceof RegExp) {
return ['RegExp']
}
}
return inferRuntimeTypeFromTypeNode(
context,
/** @type {TypeNode} */ (node)
)
}
case 'TSTypeReference': {
if (node.typeName.type === 'Identifier') {
const variable = findVariable(
getScope(context, /** @type {any} */ (node)),
node.typeName.name
)
if (variable && variable.defs.length === 1) {
const defNode = /** @type {TSESTreeNode} */ (variable.defs[0].node)
if (defNode.type === 'TSInterfaceDeclaration') {
return [`Object`]
}
if (defNode.type === 'TSTypeAliasDeclaration') {
const typeAnnotation = defNode.typeAnnotation
if (!checked.has(typeAnnotation)) {
checked.add(typeAnnotation)
return inferRuntimeType(context, typeAnnotation, checked)
}
}
if (defNode.type === 'TSEnumDeclaration') {
return inferEnumType(context, defNode)
}
}
for (const name of [
node.typeName.name,
...(node.typeName.name.startsWith('Readonly')
? [node.typeName.name.slice(8)]
: [])
]) {
switch (name) {
case 'Array':
case 'Function':
case 'Object':
case 'Set':
case 'Map':
case 'WeakSet':
case 'WeakMap':
case 'Date': {
return [name]
}
}
}
switch (node.typeName.name) {
case 'Record':
case 'Partial':
case 'Readonly':
case 'Pick':
case 'Omit':
case 'Required':
case 'InstanceType': {
return ['Object']
}
case 'Uppercase':
case 'Lowercase':
case 'Capitalize':
case 'Uncapitalize': {
return ['String']
}
case 'Parameters':
case 'ConstructorParameters': {
return ['Array']
}
case 'NonNullable': {
const typeArguments =
'typeArguments' in node
? node.typeArguments
: /** @type {any} typescript-eslint v5 */ (node).typeParameters
if (typeArguments && typeArguments.params[0]) {
return inferRuntimeType(
context,
typeArguments.params[0],
checked
).filter((t) => t !== 'null')
}
break
}
case 'Extract': {
const typeArguments =
'typeArguments' in node
? node.typeArguments
: /** @type {any} typescript-eslint v5 */ (node).typeParameters
if (typeArguments && typeArguments.params[1]) {
return inferRuntimeType(context, typeArguments.params[1], checked)
}
break
}
case 'Exclude':
case 'OmitThisParameter': {
const typeArguments =
'typeArguments' in node
? node.typeArguments
: /** @type {any} typescript-eslint v5 */ (node).typeParameters
if (typeArguments && typeArguments.params[0]) {
return inferRuntimeType(context, typeArguments.params[0], checked)
}
break
}
}
}
return inferRuntimeTypeFromTypeNode(
context,
/** @type {TypeNode} */ (node)
)
}
case 'TSUnionType':
case 'TSIntersectionType': {
return inferUnionType(node)
}
default: {
return inferRuntimeTypeFromTypeNode(
context,
/** @type {TypeNode} */ (node)
)
}
}
/**
* @param {import('@typescript-eslint/types').TSESTree.TSUnionType|import('@typescript-eslint/types').TSESTree.TSIntersectionType} node
* @returns {string[]}
*/
function inferUnionType(node) {
const types = new Set()
for (const t of node.types) {
for (const tt of inferRuntimeType(context, t, checked)) {
types.add(tt)
}
}
return [...types]
}
}
/**
* @param {import('@typescript-eslint/types').TSESTree.TSTypeLiteral} node
* @returns {string[]}
*/
function inferTypeLiteralType(node) {
const types = new Set()
for (const m of node.members) {
switch (m.type) {
case 'TSCallSignatureDeclaration':
case 'TSConstructSignatureDeclaration': {
types.add('Function')
break
}
default: {
types.add('Object')
}
}
}
return types.size > 0 ? [...types] : ['Object']
}
/**
* @param {RuleContext} context The ESLint rule context object.
* @param {import('@typescript-eslint/types').TSESTree.TSEnumDeclaration} node
* @returns {string[]}
*/
function inferEnumType(context, node) {
const types = new Set()
for (const m of node.members) {
if (m.initializer) {
if (m.initializer.type === 'Literal') {
switch (typeof m.initializer.value) {
case 'string': {
types.add('String')
break
}
case 'number':
case 'bigint': {
// Now it's a syntax error.
types.add('Number')
break
}
case 'boolean': {
// Now it's a syntax error.
types.add('Boolean')
break
}
}
} else {
for (const type of inferRuntimeTypeFromTypeNode(
context,
/** @type {Expression} */ (m.initializer)
)) {
types.add(type)
}
}
}
}
return types.size > 0 ? [...types] : ['Number']
}

View File

@@ -0,0 +1,336 @@
const {
getTypeScript,
isAny,
isUnknown,
isNever,
isNull,
isObject,
isFunction,
isStringLike,
isNumberLike,
isBooleanLike,
isBigIntLike,
isArrayLikeObject,
isReferenceObject
} = require('./typescript')
/**
* @typedef {import('@typescript-eslint/types').TSESTree.Node} TSESTreeNode
* @typedef {import('typescript').Type} Type
* @typedef {import('typescript').TypeChecker} TypeChecker
* @typedef {import('typescript').Node} TypeScriptNode
*/
/**
* @typedef {import('../index').ComponentInferTypeProp} ComponentInferTypeProp
* @typedef {import('../index').ComponentUnknownProp} ComponentUnknownProp
* @typedef {import('../index').ComponentInferTypeEmit} ComponentInferTypeEmit
* @typedef {import('../index').ComponentUnknownEmit} ComponentUnknownEmit
* @typedef {import('../index').ComponentInferTypeSlot} ComponentInferTypeSlot
* @typedef {import('../index').ComponentUnknownSlot} ComponentUnknownSlot
*/
module.exports = {
getComponentPropsFromTypeDefineTypes,
getComponentEmitsFromTypeDefineTypes,
getComponentSlotsFromTypeDefineTypes,
inferRuntimeTypeFromTypeNode
}
/**
* @typedef {object} Services
* @property {typeof import("typescript")} ts
* @property {Map<ESNode | TSNode | TSESTreeNode, TypeScriptNode>} tsNodeMap
* @property {import('typescript').TypeChecker} checker
*/
/**
* Get TypeScript parser services.
* @param {RuleContext} context The ESLint rule context object.
* @returns {Services|null}
*/
function getTSParserServices(context) {
const sourceCode = context.getSourceCode()
const tsNodeMap = sourceCode.parserServices.esTreeNodeToTSNodeMap
if (!tsNodeMap) return null
const hasFullTypeInformation =
sourceCode.parserServices.hasFullTypeInformation !== false
const checker =
(hasFullTypeInformation &&
sourceCode.parserServices.program &&
sourceCode.parserServices.program.getTypeChecker()) ||
null
if (!checker) return null
const ts = getTypeScript()
if (!ts) return null
return {
ts,
tsNodeMap,
checker
}
}
/**
* 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 {(ComponentInferTypeProp|ComponentUnknownProp)[]} Array of component props
*/
function getComponentPropsFromTypeDefineTypes(context, propsNode) {
const services = getTSParserServices(context)
const tsNode = services && services.tsNodeMap.get(propsNode)
const type = tsNode && services.checker.getTypeAtLocation(tsNode)
if (
!type ||
isAny(type) ||
isUnknown(type) ||
isNever(type) ||
isNull(type)
) {
return [
{
type: 'unknown',
propName: null,
node: propsNode
}
]
}
return [...extractRuntimeProps(type, tsNode, propsNode, services)]
}
/**
* 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 {(ComponentInferTypeEmit|ComponentUnknownEmit)[]} Array of component emits
*/
function getComponentEmitsFromTypeDefineTypes(context, emitsNode) {
const services = getTSParserServices(context)
const tsNode = services && services.tsNodeMap.get(emitsNode)
const type = tsNode && services.checker.getTypeAtLocation(tsNode)
if (
!type ||
isAny(type) ||
isUnknown(type) ||
isNever(type) ||
isNull(type)
) {
return [
{
type: 'unknown',
emitName: null,
node: emitsNode
}
]
}
return [...extractRuntimeEmits(type, tsNode, emitsNode, services)]
}
/**
* Get all slots by looking at all component's properties
* @param {RuleContext} context The ESLint rule context object.
* @param {TypeNode} slotsNode Type with slots definition
* @return {(ComponentInferTypeSlot|ComponentUnknownSlot)[]} Array of component slots
*/
function getComponentSlotsFromTypeDefineTypes(context, slotsNode) {
const services = getTSParserServices(context)
const tsNode = services && services.tsNodeMap.get(slotsNode)
const type = tsNode && services.checker.getTypeAtLocation(tsNode)
if (
!type ||
isAny(type) ||
isUnknown(type) ||
isNever(type) ||
isNull(type)
) {
return [
{
type: 'unknown',
slotName: null,
node: slotsNode
}
]
}
return [...extractRuntimeSlots(type, slotsNode)]
}
/**
* @param {RuleContext} context The ESLint rule context object.
* @param {TypeNode|Expression} node
* @returns {string[]}
*/
function inferRuntimeTypeFromTypeNode(context, node) {
const services = getTSParserServices(context)
const tsNode = services && services.tsNodeMap.get(node)
const type = tsNode && services.checker.getTypeAtLocation(tsNode)
if (!type) {
return ['null']
}
return inferRuntimeTypeInternal(type, services)
}
/**
* @param {Type} type
* @param {TypeScriptNode} tsNode
* @param {TypeNode} propsNode Type with props definition
* @param {Services} services
* @returns {IterableIterator<ComponentInferTypeProp>}
*/
function* extractRuntimeProps(type, tsNode, propsNode, services) {
const { ts, checker } = services
for (const property of type.getProperties()) {
const isOptional = (property.flags & ts.SymbolFlags.Optional) !== 0
const name = property.getName()
const type = checker.getTypeOfSymbolAtLocation(property, tsNode)
yield {
type: 'infer-type',
propName: name,
required: !isOptional,
node: propsNode,
types: inferRuntimeTypeInternal(type, services)
}
}
}
/**
* @param {Type} type
* @param {Services} services
* @returns {string[]}
*/
function inferRuntimeTypeInternal(type, services) {
const { checker } = services
/** @type {Set<string>} */
const types = new Set()
// handle generic parameter types
if (type.isTypeParameter()) {
const constraint = type.getConstraint()
if (constraint) {
for (const t of inferRuntimeTypeInternal(constraint, services)) {
types.add(t)
}
}
return [...types]
}
for (const targetType of iterateTypes(checker.getNonNullableType(type))) {
if (
isAny(targetType) ||
isUnknown(targetType) ||
isNever(targetType) ||
isNull(targetType)
) {
types.add('null')
} else if (isStringLike(targetType)) {
types.add('String')
} else if (isNumberLike(targetType) || isBigIntLike(targetType)) {
types.add('Number')
} else if (isBooleanLike(targetType)) {
types.add('Boolean')
} else if (isFunction(targetType)) {
types.add('Function')
} else if (
isArrayLikeObject(targetType) ||
(targetType.isClassOrInterface() &&
['Array', 'ReadonlyArray'].includes(
checker.getFullyQualifiedName(targetType.symbol)
))
) {
types.add('Array')
} else if (isObject(targetType)) {
types.add('Object')
}
}
if (types.size <= 0) types.add('null')
return [...types]
}
/**
* @param {Type} type
* @param {TypeScriptNode} tsNode
* @param {TypeNode} emitsNode Type with emits definition
* @param {Services} services
* @returns {IterableIterator<ComponentInferTypeEmit|ComponentUnknownEmit>}
*/
function* extractRuntimeEmits(type, tsNode, emitsNode, services) {
const { checker } = services
if (isFunction(type)) {
for (const signature of type.getCallSignatures()) {
const param = signature.getParameters()[0]
if (!param) {
yield {
type: 'unknown',
emitName: null,
node: emitsNode
}
continue
}
const type = checker.getTypeOfSymbolAtLocation(param, tsNode)
for (const targetType of iterateTypes(type)) {
yield targetType.isStringLiteral()
? {
type: 'infer-type',
emitName: targetType.value,
node: emitsNode
}
: {
type: 'unknown',
emitName: null,
node: emitsNode
}
}
}
} else if (isObject(type)) {
for (const property of type.getProperties()) {
const name = property.getName()
yield {
type: 'infer-type',
emitName: name,
node: emitsNode
}
}
} else {
yield {
type: 'unknown',
emitName: null,
node: emitsNode
}
}
}
/**
* @param {Type} type
* @param {TypeNode} slotsNode Type with slots definition
* @returns {IterableIterator<ComponentInferTypeSlot>}
*/
function* extractRuntimeSlots(type, slotsNode) {
for (const property of type.getProperties()) {
const name = property.getName()
yield {
type: 'infer-type',
slotName: name,
node: slotsNode
}
}
}
/**
* @param {Type} type
* @returns {Iterable<Type>}
*/
function* iterateTypes(type) {
if (isReferenceObject(type) && type.target !== type) {
yield* iterateTypes(type.target)
} else if (type.isUnion() && !isBooleanLike(type)) {
for (const t of type.types) {
yield* iterateTypes(t)
}
} else {
yield type
}
}

View File

@@ -0,0 +1,214 @@
/**
* @typedef {typeof import("typescript")} TypeScript
* @typedef {import("typescript").Type} Type
* @typedef {import("typescript").ObjectType} ObjectType
* @typedef {import("typescript").InterfaceType} InterfaceType
* @typedef {import("typescript").TypeReference} TypeReference
* @typedef {import("typescript").UnionOrIntersectionType} UnionOrIntersectionType
* @typedef {import("typescript").TypeParameter} TypeParameter
*/
/** @type {TypeScript | undefined} */
let cacheTypeScript
module.exports = {
getTypeScript,
isObject,
isAny,
isUnknown,
isNever,
isNull,
isFunction,
isArrayLikeObject,
isStringLike,
isNumberLike,
isBooleanLike,
isBigIntLike,
isReferenceObject,
extractTypeFlags,
extractObjectFlags
}
/**
* Get TypeScript instance
*/
function getTypeScript() {
if (cacheTypeScript) {
return cacheTypeScript
}
try {
return (cacheTypeScript = require('typescript'))
} catch (error) {
if (/** @type {any} */ (error).code === 'MODULE_NOT_FOUND') {
return undefined
}
throw error
}
}
/**
* For debug
* @param {Type} tsType
* @returns {string[]}
*/
function extractTypeFlags(tsType) {
const ts = /** @type {TypeScript} */ (getTypeScript())
/** @type {string[]} */
const result = []
const keys = /** @type {(keyof (typeof ts.TypeFlags))[]} */ (
Object.keys(ts.TypeFlags)
)
for (const k of keys) {
if ((tsType.flags & ts.TypeFlags[k]) !== 0) {
result.push(k)
}
}
return result
}
/**
* For debug
* @param {Type} tsType
* @returns {string[]}
*/
function extractObjectFlags(tsType) {
if (!isObject(tsType)) {
return []
}
const ts = /** @type {TypeScript} */ (getTypeScript())
/** @type {string[]} */
const result = []
const keys = /** @type {(keyof (typeof ts.ObjectFlags))[]} */ (
Object.keys(ts.ObjectFlags)
)
for (const k of keys) {
if ((tsType.objectFlags & ts.ObjectFlags[k]) !== 0) {
result.push(k)
}
}
return result
}
/**
* Check if a given type is an object type or not.
* @param {Type} tsType The type to check.
* @returns {tsType is ObjectType}
*/
function isObject(tsType) {
const ts = /** @type {TypeScript} */ (getTypeScript())
return (tsType.flags & ts.TypeFlags.Object) !== 0
}
/**
* Check if a given type is an array-like type or not.
* @param {Type} tsType The type to check.
*/
function isArrayLikeObject(tsType) {
const ts = /** @type {TypeScript} */ (getTypeScript())
return (
isObject(tsType) &&
(tsType.objectFlags &
(ts.ObjectFlags.ArrayLiteral |
ts.ObjectFlags.EvolvingArray |
ts.ObjectFlags.Tuple)) !==
0
)
}
/**
* Check if a given type is an any type or not.
* @param {Type} tsType The type to check.
*/
function isAny(tsType) {
const ts = /** @type {TypeScript} */ (getTypeScript())
return (tsType.flags & ts.TypeFlags.Any) !== 0
}
/**
* Check if a given type is an unknown type or not.
* @param {Type} tsType The type to check.
*/
function isUnknown(tsType) {
const ts = /** @type {TypeScript} */ (getTypeScript())
return (tsType.flags & ts.TypeFlags.Unknown) !== 0
}
/**
* Check if a given type is a never type or not.
* @param {Type} tsType The type to check.
*/
function isNever(tsType) {
const ts = /** @type {TypeScript} */ (getTypeScript())
return (tsType.flags & ts.TypeFlags.Never) !== 0
}
/**
* Check if a given type is an null type or not.
* @param {Type} tsType The type to check.
*/
function isNull(tsType) {
const ts = /** @type {TypeScript} */ (getTypeScript())
return (tsType.flags & ts.TypeFlags.Null) !== 0
}
/**
* Check if a given type is a string-like type or not.
* @param {Type} tsType The type to check.
* @returns {boolean} `true` if the type is a string-like type.
*/
function isStringLike(tsType) {
const ts = /** @type {TypeScript} */ (getTypeScript())
return (tsType.flags & ts.TypeFlags.StringLike) !== 0
}
/**
* Check if a given type is an number-like type or not.
* @param {Type} tsType The type to check.
* @returns {boolean} `true` if the type is a number-like type.
*/
function isNumberLike(tsType) {
const ts = /** @type {TypeScript} */ (getTypeScript())
return (tsType.flags & ts.TypeFlags.NumberLike) !== 0
}
/**
* Check if a given type is an boolean-like type or not.
* @param {Type} tsType The type to check.
* @returns {boolean} `true` if the type is a boolean-like type.
*/
function isBooleanLike(tsType) {
const ts = /** @type {TypeScript} */ (getTypeScript())
return (tsType.flags & ts.TypeFlags.BooleanLike) !== 0
}
/**
* Check if a given type is an bigint-like type or not.
* @param {Type} tsType The type to check.
* @returns {boolean} `true` if the type is a bigint-like type.
*/
function isBigIntLike(tsType) {
const ts = /** @type {TypeScript} */ (getTypeScript())
return (tsType.flags & ts.TypeFlags.BigIntLike) !== 0
}
/**
* Check if a given type is a reference type or not.
* @param {Type} tsType The type to check.
* @returns {tsType is TypeReference} `true` if the type is a reference type.
*/
function isReferenceObject(tsType) {
const ts = /** @type {TypeScript} */ (getTypeScript())
return (
isObject(tsType) && (tsType.objectFlags & ts.ObjectFlags.Reference) !== 0
)
}
/**
* Check if a given type is `function` or not.
* @param {Type} tsType The type to check.
*/
function isFunction(tsType) {
const ts = /** @type {TypeScript} */ (getTypeScript())
if (
tsType.symbol &&
(tsType.symbol.flags &
(ts.SymbolFlags.Function | ts.SymbolFlags.Method)) !==
0
) {
return true
}
const signatures = tsType.getCallSignatures()
return signatures.length > 0
}

View File

@@ -0,0 +1,18 @@
[
"area",
"base",
"br",
"col",
"embed",
"hr",
"img",
"input",
"keygen",
"link",
"menuitem",
"meta",
"param",
"source",
"track",
"wbr"
]

View File

@@ -0,0 +1 @@
module.exports = ['template', 'slot', 'component']

View File

@@ -0,0 +1,62 @@
{
"nuxt": [
"asyncData",
"fetch",
"head",
"key",
"layout",
"loading",
"middleware",
"scrollToTop",
"transition",
"validate",
"watchQuery"
],
"vue-router": ["beforeRouteEnter", "beforeRouteUpdate", "beforeRouteLeave"],
"vue": [
"data",
"props",
"propsData",
"computed",
"methods",
"watch",
"el",
"template",
"render",
"renderError",
"staticRenderFns",
"beforeCreate",
"created",
"beforeDestroy",
"destroyed",
"beforeMount",
"mounted",
"beforeUpdate",
"updated",
"activated",
"deactivated",
"errorCaptured",
"serverPrefetch",
"directives",
"components",
"transitions",
"filters",
"provide",
"inject",
"model",
"parent",
"mixins",
"name",
"extends",
"delimiters",
"comments",
"inheritAttrs",
"setup",
"emits",
"beforeUnmount",
"unmounted",
"renderTracked",
"renderTriggered"
]
}

View File

@@ -0,0 +1,26 @@
[
"$data",
"$props",
"$el",
"$options",
"$parent",
"$root",
"$children",
"$slots",
"$scopedSlots",
"$refs",
"$isServer",
"$attrs",
"$listeners",
"$watch",
"$set",
"$delete",
"$on",
"$once",
"$off",
"$emit",
"$mount",
"$forceUpdate",
"$nextTick",
"$destroy"
]

View File

@@ -0,0 +1,12 @@
module.exports = [
'template',
'slot',
'component',
'Component',
'transition',
'Transition',
'transition-group',
'TransitionGroup',
'keep-alive',
'KeepAlive'
]

View File

@@ -0,0 +1,7 @@
module.exports = [
...require('./vue2-builtin-components'),
'teleport',
'Teleport',
'suspense',
'Suspense'
]

View File

@@ -0,0 +1,356 @@
[
"ComputedGetter",
"ComputedRef",
"ComputedSetter",
"CustomRefFactory",
"DebuggerEvent",
"DebuggerEventExtraInfo",
"DebuggerOptions",
"DeepReadonly",
"EffectScheduler",
"EffectScope",
"MaybeRef",
"MaybeRefOrGetter",
"Raw",
"Reactive",
"ReactiveEffect",
"ReactiveEffectOptions",
"ReactiveEffectRunner",
"ReactiveFlags",
"Ref",
"ShallowReactive",
"ShallowRef",
"ShallowUnwrapRef",
"ToRef",
"ToRefs",
"TrackOpTypes",
"TriggerOpTypes",
"UnwrapNestedRefs",
"UnwrapRef",
"WatchCallback",
"WatchEffect",
"WatchHandle",
"WatchSource",
"WatchStopHandle",
"WritableComputedOptions",
"WritableComputedRef",
"customRef",
"effect",
"effectScope",
"getCurrentScope",
"getCurrentWatcher",
"isProxy",
"isReactive",
"isReadonly",
"isRef",
"isShallow",
"markRaw",
"onScopeDispose",
"onWatcherCleanup",
"proxyRefs",
"reactive",
"readonly",
"ref",
"shallowReactive",
"shallowReadonly",
"shallowRef",
"stop",
"toRaw",
"toRef",
"toRefs",
"toValue",
"triggerRef",
"unref",
"camelize",
"capitalize",
"normalizeClass",
"normalizeProps",
"normalizeStyle",
"toDisplayString",
"toHandlerKey",
"computed",
"Slot",
"Slots",
"SlotsType",
"nextTick",
"queuePostFlushCb",
"ComponentPropsOptions",
"ComponentObjectPropsOptions",
"Prop",
"PropType",
"ExtractPropTypes",
"ExtractPublicPropTypes",
"ExtractDefaultPropTypes",
"defineProps",
"DefineProps",
"defineEmits",
"ComponentTypeEmits",
"defineExpose",
"defineOptions",
"defineSlots",
"ModelRef",
"defineModel",
"withDefaults",
"useSlots",
"useAttrs",
"ObjectEmitsOptions",
"EmitsOptions",
"EmitsToProps",
"ShortEmitsToObject",
"EmitFn",
"DirectiveBinding",
"DirectiveHook",
"ObjectDirective",
"FunctionDirective",
"Directive",
"DirectiveArguments",
"withDirectives",
"ComponentCustomProperties",
"CreateComponentPublicInstance",
"CreateComponentPublicInstanceWithMixins",
"ComponentPublicInstance",
"SuspenseProps",
"Suspense",
"SuspenseBoundary",
"RootHydrateFunction",
"BaseTransitionProps",
"TransitionHooks",
"TransitionState",
"useTransitionState",
"BaseTransitionPropsValidators",
"BaseTransition",
"resolveTransitionHooks",
"setTransitionHooks",
"getTransitionRawChildren",
"Renderer",
"HydrationRenderer",
"ElementNamespace",
"RootRenderFunction",
"RendererOptions",
"RendererNode",
"RendererElement",
"createRenderer",
"createHydrationRenderer",
"KeepAliveProps",
"KeepAlive",
"onActivated",
"onDeactivated",
"onBeforeMount",
"onMounted",
"onBeforeUpdate",
"onUpdated",
"onBeforeUnmount",
"onUnmounted",
"onServerPrefetch",
"onRenderTriggered",
"onRenderTracked",
"onErrorCaptured",
"ComponentCustomOptions",
"RenderFunction",
"ComponentOptionsBase",
"RuntimeCompilerOptions",
"ComponentOptions",
"ComponentOptionsMixin",
"ComputedOptions",
"MethodOptions",
"ComponentProvideOptions",
"ComponentInjectOptions",
"ComponentOptionsWithoutProps",
"ComponentOptionsWithArrayProps",
"ComponentOptionsWithObjectProps",
"InjectionKey",
"provide",
"inject",
"hasInjectionContext",
"PublicProps",
"DefineComponent",
"DefineSetupFnComponent",
"defineComponent",
"App",
"OptionMergeFunction",
"AppConfig",
"AppContext",
"ObjectPlugin",
"FunctionPlugin",
"Plugin",
"CreateAppFunction",
"TeleportProps",
"Teleport",
"resolveComponent",
"resolveDynamicComponent",
"resolveDirective",
"Fragment",
"Text",
"Comment",
"Static",
"VNodeTypes",
"VNodeRef",
"VNodeProps",
"VNodeArrayChildren",
"VNodeChild",
"VNodeNormalizedChildren",
"VNode",
"openBlock",
"setBlockTracking",
"createElementBlock",
"createBlock",
"isVNode",
"transformVNodeArgs",
"createBaseVNode",
"createVNode",
"guardReactiveProps",
"cloneVNode",
"createTextVNode",
"createStaticVNode",
"createCommentVNode",
"mergeProps",
"ComponentInstance",
"ComponentCustomProps",
"GlobalDirectives",
"GlobalComponents",
"AllowedComponentProps",
"FunctionalComponent",
"ConcreteComponent",
"Component",
"SetupContext",
"ComponentInternalInstance",
"getCurrentInstance",
"registerRuntimeCompiler",
"isRuntimeOnly",
"ComponentCustomElementInterface",
"WatchEffectOptions",
"WatchOptions",
"watchEffect",
"watchPostEffect",
"watchSyncEffect",
"MultiWatchSources",
"watch",
"HydrationStrategy",
"HydrationStrategyFactory",
"hydrateOnIdle",
"hydrateOnVisible",
"hydrateOnMediaQuery",
"hydrateOnInteraction",
"AsyncComponentLoader",
"AsyncComponentOptions",
"defineAsyncComponent",
"useModel",
"TemplateRef",
"useTemplateRef",
"useId",
"h",
"ssrContextKey",
"useSSRContext",
"ErrorCodes",
"callWithErrorHandling",
"callWithAsyncErrorHandling",
"handleError",
"initCustomFormatter",
"HMRRuntime",
"pushScopeId",
"popScopeId",
"withScopeId",
"withCtx",
"renderList",
"toHandlers",
"renderSlot",
"createSlots",
"withMemo",
"isMemoSame",
"LegacyConfig",
"CompatVue",
"version",
"warn",
"devtools",
"setDevtoolsHook",
"DeprecationTypes",
"createElementVNode",
"WatchOptionsBase",
"TransitionProps",
"Transition",
"TransitionGroupProps",
"TransitionGroup",
"vShow",
"withModifiers",
"withKeys",
"vModelText",
"vModelCheckbox",
"vModelRadio",
"vModelSelect",
"vModelDynamic",
"VueElementConstructor",
"CustomElementOptions",
"defineCustomElement",
"defineSSRCustomElement",
"VueElement",
"useHost",
"useShadowRoot",
"useCssModule",
"useCssVars",
"CSSProperties",
"AriaAttributes",
"StyleValue",
"HTMLAttributes",
"AnchorHTMLAttributes",
"AreaHTMLAttributes",
"AudioHTMLAttributes",
"BaseHTMLAttributes",
"BlockquoteHTMLAttributes",
"ButtonHTMLAttributes",
"CanvasHTMLAttributes",
"ColHTMLAttributes",
"ColgroupHTMLAttributes",
"DataHTMLAttributes",
"DetailsHTMLAttributes",
"DelHTMLAttributes",
"DialogHTMLAttributes",
"EmbedHTMLAttributes",
"FieldsetHTMLAttributes",
"FormHTMLAttributes",
"HtmlHTMLAttributes",
"IframeHTMLAttributes",
"ImgHTMLAttributes",
"InsHTMLAttributes",
"InputTypeHTMLAttribute",
"InputHTMLAttributes",
"KeygenHTMLAttributes",
"LabelHTMLAttributes",
"LiHTMLAttributes",
"LinkHTMLAttributes",
"MapHTMLAttributes",
"MenuHTMLAttributes",
"MediaHTMLAttributes",
"MetaHTMLAttributes",
"MeterHTMLAttributes",
"QuoteHTMLAttributes",
"ObjectHTMLAttributes",
"OlHTMLAttributes",
"OptgroupHTMLAttributes",
"OptionHTMLAttributes",
"OutputHTMLAttributes",
"ParamHTMLAttributes",
"ProgressHTMLAttributes",
"ScriptHTMLAttributes",
"SelectHTMLAttributes",
"SourceHTMLAttributes",
"StyleHTMLAttributes",
"TableHTMLAttributes",
"TextareaHTMLAttributes",
"TdHTMLAttributes",
"ThHTMLAttributes",
"TimeHTMLAttributes",
"TrackHTMLAttributes",
"VideoHTMLAttributes",
"WebViewHTMLAttributes",
"SVGAttributes",
"IntrinsicElementAttributes",
"Events",
"ReservedProps",
"NativeElements",
"render",
"hydrate",
"createApp",
"createSSRApp",
"compileToFunction",
"compile"
]