UNPKG

@stencil/eslint-plugin

Version:

ESLint rules specific to Stencil JS projects.

1 lines 103 kB
{"version":3,"file":"index.cjs","names":["ts","originalNode: ts.Node","node: any","decoratorName?: string","decoratorName","dec: any","decorator: any","a: any","propName: string","componentNode: any","TOKEN_TO_TEXT: { readonly [P in ts.SyntaxKind]?: string }","rule: Rule.RuleModule","decoratorNode: any","rule","rule: Rule.RuleModule","node: any","rule","DEFAULTS","rule: Rule.RuleModule","node: any","t: string","rule","rule: Rule.RuleModule","node: any","rule","rule: Rule.RuleModule","node: any","rule","DEFAULTS: DecoratorsStyleOptions","rule: Rule.RuleModule","DEFAULTS","decorator: any","node: any","decorators: any[]","rule","rule: Rule.RuleModule","tag: string","node: any","rule","rule: Rule.RuleModule","node: any","rule","rule: Rule.RuleModule","node: any","rule","rule: Rule.RuleModule","node: any","GLOBAL_ATTRIBUTES","attributeName: string","rule","rule: Rule.RuleModule","node: any","dec: any","rule","rule: Rule.RuleModule","node: any","dec: any","rule","rule: Rule.RuleModule","node: any","eventName","rule","rule: Rule.RuleModule","node: any","rule","rule: Rule.RuleModule","node: any","ts","rule","rule: Rule.RuleModule","node: any","rule","rule: Rule.RuleModule","node: any","jsdoc: any","tag: any","rule","rule: Rule.RuleModule","node: any","t: string","rule","rule: Rule.RuleModule","node: any","decoratorName","JSDOM","prop: string","memberName: string","rule","rule: Rule.RuleModule","node: any","ts","rule","rule: Rule.RuleModule","node: any","rule","rule: Rule.RuleModule","node: any","n: any","rule","rule: Rule.RuleModule","sourceFile: ts.SourceFile","node: ts.Node","ts","node: ts.Expression","location: Location","node: any","ruleArguments: string[]","strictNullChecks: boolean","name: string","type: ts.Type","options: Options","type: ts.UnionType","kind: TypeKind","isInUnion: boolean","flags: ts.TypeFlags","node: ts.BinaryExpression","parts: string[]","n: Location","ty: TypeFailure","unionType: boolean","obj: any","flag: any","rule","rule: Rule.RuleModule","node: any","rule","rule: Rule.RuleModule","node: any","SUGGESTIONS: {[importName: string]: string}","banSideEffects","banDefaultTrue","banExportedConstEnums","dependencySuggestions","strictBooleanConditions","asyncMethods","banPrefix","classPattern","decoratorsContext","decoratorsStyle","elementType","hostDataDeprecated","methodsMustBePublic","noUnusedWatch","ownMethodsMustBePrivate","ownPropsMustBePrivate","preferVdomListener","propsMustBePublic","propsMustBeReadonly","renderReturnsHost","requiredJsdoc","requiredPrefix","reservedMemberNames","singleExport","strictMutable","flatBase: Linter.Config","configs","flatRecommended: Linter.Config","react","flatStrict: Linter.Config"],"sources":["../src/utils.ts","../src/rules/async-methods.ts","../src/rules/ban-default-true.ts","../src/rules/ban-prefix.ts","../src/rules/class-pattern.ts","../src/rules/decorators-context.ts","../src/rules/decorators-style.ts","../src/rules/element-type.ts","../src/rules/host-data-deprecated.ts","../src/rules/methods-must-be-public.ts","../src/rules/no-unused-watch.ts","../src/rules/own-methods-must-be-private.ts","../src/rules/own-props-must-be-private.ts","../src/rules/prefer-vdom-listener.ts","../src/rules/props-must-be-public.ts","../src/rules/props-must-be-readonly.ts","../src/rules/render-returns-host.ts","../src/rules/required-jsdoc.ts","../src/rules/required-prefix.ts","../src/rules/reserved-member-names.ts","../src/rules/single-export.ts","../src/rules/strict-mutable.ts","../src/rules/ban-side-effects.ts","../src/rules/strict-boolean-conditions.ts","../src/rules/ban-exported-const-enums.ts","../src/rules/dependency-suggestions.ts","../src/rules/index.ts","../src/configs/base.ts","../src/configs/recommended.ts","../src/configs/strict.ts","../src/configs/index.ts","../src/index.ts"],"sourcesContent":["import ts from 'typescript';\nimport { getStaticValue } from 'eslint-utils';\n\nconst SyntaxKind = ts.SyntaxKind;\n\nexport function isPrivate(originalNode: ts.Node) {\n const modifiers = ts.canHaveModifiers(originalNode)\n ? ts.getModifiers(originalNode)\n : undefined;\n if (modifiers) {\n return modifiers.some(\n (m) =>\n m.kind === ts.SyntaxKind.PrivateKeyword ||\n m.kind === ts.SyntaxKind.ProtectedKeyword\n );\n }\n // detect private identifier (#)\n const firstChildNode = originalNode.getChildAt(0);\n return firstChildNode ? firstChildNode.kind === SyntaxKind.PrivateIdentifier : false;\n}\n\nexport function getDecoratorList(\n originalNode: ts.Node\n): readonly ts.Decorator[] | undefined {\n const decorators = ts.canHaveDecorators(originalNode)\n ? ts.getDecorators(originalNode)\n : undefined;\n return decorators;\n}\n\nexport function getDecorator(node: any, decoratorName?: string): any | any[] {\n if (decoratorName) {\n return node.decorators && node.decorators.find(isDecoratorNamed(decoratorName));\n }\n return node.decorators ? node.decorators.filter((dec: any) => dec.expression) : [];\n}\n\nexport function parseDecorator(decorator: any) {\n if (decorator && decorator.expression && decorator.expression.type === 'CallExpression') {\n return decorator.expression.arguments.map((a: any) => {\n const parsed = getStaticValue(a);\n return parsed ? parsed.value : undefined;\n });\n }\n return [];\n}\n\nexport function decoratorName(dec: any): string {\n return dec.expression && dec.expression.callee.name;\n}\n\nexport function isDecoratorNamed(propName: string) {\n return (dec: any): boolean => {\n return decoratorName(dec) === propName;\n };\n}\n\nexport function stencilComponentContext() {\n let componentNode: any;\n return {\n rules: {\n 'ClassDeclaration': (node: any) => {\n const component = getDecorator(node, 'Component');\n if (component) {\n componentNode = component;\n }\n },\n 'ClassDeclaration:exit': (node: any) => {\n if (componentNode === node) {\n componentNode = undefined;\n }\n }\n },\n isComponent() {\n return !!componentNode;\n }\n };\n}\n\nexport function getType(node: any) {\n return node.typeAnnotation?.typeAnnotation?.typeName?.name;\n}\n\nexport const stencilDecorators = ['Component', 'Prop', 'State', 'Watch', 'Element', 'Method', 'Event', 'Listen', 'AttachInternals'];\n\nexport const stencilLifecycle = [\n 'connectedCallback',\n 'disconnectedCallback',\n 'componentWillLoad',\n 'componentDidLoad',\n 'componentWillRender',\n 'componentDidRender',\n 'componentShouldUpdate',\n 'componentWillUpdate',\n 'componentDidUpdate',\n 'formAssociatedCallback',\n 'formDisabledCallback',\n 'formResetCallback',\n 'formStateRestoreCallback',\n 'render'\n];\n\nconst TOKEN_TO_TEXT: { readonly [P in ts.SyntaxKind]?: string } = {\n [SyntaxKind.OpenBraceToken]: '{',\n [SyntaxKind.CloseBraceToken]: '}',\n [SyntaxKind.OpenParenToken]: '(',\n [SyntaxKind.CloseParenToken]: ')',\n [SyntaxKind.OpenBracketToken]: '[',\n [SyntaxKind.CloseBracketToken]: ']',\n [SyntaxKind.DotToken]: '.',\n [SyntaxKind.DotDotDotToken]: '...',\n [SyntaxKind.SemicolonToken]: ',',\n [SyntaxKind.CommaToken]: ',',\n [SyntaxKind.LessThanToken]: '<',\n [SyntaxKind.GreaterThanToken]: '>',\n [SyntaxKind.LessThanEqualsToken]: '<=',\n [SyntaxKind.GreaterThanEqualsToken]: '>=',\n [SyntaxKind.EqualsEqualsToken]: '==',\n [SyntaxKind.ExclamationEqualsToken]: '!=',\n [SyntaxKind.EqualsEqualsEqualsToken]: '===',\n [SyntaxKind.InstanceOfKeyword]: 'instanceof',\n [SyntaxKind.ExclamationEqualsEqualsToken]: '!==',\n [SyntaxKind.EqualsGreaterThanToken]: '=>',\n [SyntaxKind.PlusToken]: '+',\n [SyntaxKind.MinusToken]: '-',\n [SyntaxKind.AsteriskToken]: '*',\n [SyntaxKind.AsteriskAsteriskToken]: '**',\n [SyntaxKind.SlashToken]: '/',\n [SyntaxKind.PercentToken]: '%',\n [SyntaxKind.PlusPlusToken]: '++',\n [SyntaxKind.MinusMinusToken]: '--',\n [SyntaxKind.LessThanLessThanToken]: '<<',\n [SyntaxKind.LessThanSlashToken]: '</',\n [SyntaxKind.GreaterThanGreaterThanToken]: '>>',\n [SyntaxKind.GreaterThanGreaterThanGreaterThanToken]: '>>>',\n [SyntaxKind.AmpersandToken]: '&',\n [SyntaxKind.BarToken]: '|',\n [SyntaxKind.CaretToken]: '^',\n [SyntaxKind.ExclamationToken]: '!',\n [SyntaxKind.TildeToken]: '~',\n [SyntaxKind.AmpersandAmpersandToken]: '&&',\n [SyntaxKind.BarBarToken]: '||',\n [SyntaxKind.QuestionToken]: '?',\n [SyntaxKind.ColonToken]: ':',\n [SyntaxKind.EqualsToken]: '=',\n [SyntaxKind.PlusEqualsToken]: '+=',\n [SyntaxKind.MinusEqualsToken]: '-=',\n [SyntaxKind.AsteriskEqualsToken]: '*=',\n [SyntaxKind.AsteriskAsteriskEqualsToken]: '**=',\n [SyntaxKind.SlashEqualsToken]: '/=',\n [SyntaxKind.PercentEqualsToken]: '%=',\n [SyntaxKind.LessThanLessThanEqualsToken]: '<<=',\n [SyntaxKind.GreaterThanGreaterThanEqualsToken]: '>>=',\n [SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken]: '>>>=',\n [SyntaxKind.AmpersandEqualsToken]: '&=',\n [SyntaxKind.BarEqualsToken]: '|=',\n [SyntaxKind.CaretEqualsToken]: '^=',\n [SyntaxKind.AtToken]: '@',\n [SyntaxKind.InKeyword]: 'in',\n [SyntaxKind.UniqueKeyword]: 'unique',\n [SyntaxKind.KeyOfKeyword]: 'keyof',\n [SyntaxKind.NewKeyword]: 'new',\n [SyntaxKind.ImportKeyword]: 'import',\n [SyntaxKind.ReadonlyKeyword]: 'readonly'\n};\n\n/**\n * Returns the string form of the given TSToken SyntaxKind\n * @param kind the token's SyntaxKind\n * @returns the token applicable token as a string\n */\nexport function getTextForTokenKind(kind: ts.SyntaxKind): string | undefined {\n return TOKEN_TO_TEXT[kind];\n}","import type { Rule } from 'eslint';\nimport ts from 'typescript';\nimport { stencilComponentContext } from '../utils';\nimport { isThenableType } from 'tsutils';\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches Stencil public methods that are not async.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [],\n type: 'problem',\n fixable: 'code'\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n const parserServices = context.sourceCode.parserServices;\n const typeChecker = parserServices.program.getTypeChecker() as ts.TypeChecker;\n\n return {\n ...stencil.rules,\n 'MethodDefinition > Decorator[expression.callee.name=Method]': (decoratorNode: any) => {\n if (!stencil.isComponent()) {\n return;\n }\n const node = decoratorNode.parent;\n const method = parserServices.esTreeNodeToTSNodeMap.get(node);\n const signature = typeChecker.getSignatureFromDeclaration(method);\n const returnType = typeChecker.getReturnTypeOfSignature(signature!);\n if (!isThenableType(typeChecker, method, returnType)) {\n const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node) as ts.Node;\n const text = String(originalNode.getFullText());\n context.report({\n node: node.key,\n message: `External @Method() ${node.key.name}() must return a Promise. Consider prefixing the method with async, such as @Method() async ${node.key.name}().`,\n fix(fixer) {\n const result = text\n // a newline + whitespace preceding `@Method` may be captured, remove it\n .trimLeft()\n // capture the number of following the decorator to know how far to indent the `async` method\n .replace(/@Method\\(\\)\\n(\\s*)/, '@Method()\\n$1async ')\n // replace any inlined @Method decorators\n .replace('@Method() ', '@Method() async')\n // swap the order of the `async` and `public` keywords\n .replace('async public', 'public async')\n // swap the order of the `async` and `private` keywords\n .replace('async private', 'private async');\n return fixer.replaceText(node, result);\n }\n });\n }\n }\n };\n }\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport { getDecorator, stencilComponentContext } from '../utils';\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches Stencil Props defaulting to true.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [],\n type: 'problem',\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n\n return {\n ...stencil.rules,\n 'PropertyDefinition': (node: any) => {\n const propDecorator = getDecorator(node, 'Prop');\n if (!(stencil.isComponent() && propDecorator)) {\n return;\n }\n\n if (node.value?.value === true) {\n context.report({\n node: node,\n message: `Boolean properties decorated with @Prop() should default to false`\n });\n }\n }\n };\n }\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport { getDecorator, parseDecorator, stencilComponentContext } from '../utils';\n\nconst DEFAULTS = ['stencil', 'stnl', 'st'];\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches usages banned prefix in component tag name.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [\n {\n type: 'array',\n items: {\n type: 'string'\n },\n minLength: 1,\n additionalProperties: false\n }\n ],\n type: 'problem'\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n\n return {\n ...stencil.rules,\n 'ClassDeclaration': (node: any) => {\n const component = getDecorator(node, 'Component');\n if (!component) {\n return;\n }\n const [opts] = parseDecorator(component);\n if (!opts || !opts.tag) {\n return;\n }\n const tag = opts.tag;\n const options = context.options[0] || DEFAULTS;\n const match = options.some((t: string) => tag.startsWith(`${t}-`));\n\n if (match) {\n context.report({\n node: node,\n message: `The component with tag name ${tag} have a banned prefix.`\n });\n }\n }\n };\n }\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport { getDecorator, parseDecorator, stencilComponentContext } from '../utils';\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches usages of non valid class names.',\n category: 'Possible Errors',\n recommended: false\n },\n schema: [\n {\n type: 'object',\n properties: {\n pattern: {\n type: 'string'\n },\n ignoreCase: {\n type: 'boolean'\n }\n },\n additionalProperties: false\n }],\n type: 'problem'\n\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n const parserServices = context.sourceCode.parserServices;\n\n return {\n ...stencil.rules,\n 'ClassDeclaration': (node: any) => {\n const component = getDecorator(node, 'Component');\n const options = context.options[0];\n const { pattern, ignoreCase } = options || {};\n if (!component || !options || !pattern) {\n return;\n }\n const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node);\n const className = originalNode.symbol.escapedName;\n const regExp = new RegExp(pattern, ignoreCase ? 'i' : undefined);\n\n if (!regExp.test(className)) {\n const [opts] = parseDecorator(component);\n if (!opts || !opts.tag) {\n return;\n }\n context.report({\n node: node,\n message: `The class name in component with tag name ${opts.tag} is not valid (${regExp}).`\n });\n }\n }\n };\n }\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport { stencilComponentContext } from '../utils';\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches Stencil Decorators used in incorrect locations.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [],\n type: 'problem'\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n\n return {\n ...stencil.rules,\n 'Decorator': (node: any) => {\n if (!stencil.isComponent()) {\n return;\n }\n if (node.expression && node.expression.callee) {\n const decName = node.expression.callee.name;\n if (\n decName === 'Prop' ||\n decName === 'State' ||\n decName === 'Element' ||\n decName === 'Event'\n ) {\n if (\n node.parent.type !== 'PropertyDefinition' && \n (\n node.parent.type === 'MethodDefinition' && \n ['get', 'set'].indexOf(node.parent.kind) < 0\n )\n ) { \n context.report({\n node: node,\n message: `The @${decName} decorator can only be applied to class properties.`\n });\n }\n } else if (\n decName === 'Method' ||\n decName === 'Watch' ||\n decName === 'Listen'\n ) {\n if (node.parent.type !== 'MethodDefinition') {\n context.report({\n node: node,\n message: `The @${decName} decorator can only be applied to class methods.`\n });\n }\n } else if (decName === 'Component') {\n if (node.parent.type !== 'ClassDeclaration') {\n context.report({\n node: node,\n message: `The @${decName} decorator can only be applied to a class.`\n });\n }\n }\n }\n }\n };\n }\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport ts from 'typescript';\nimport { decoratorName, getDecorator, stencilComponentContext, stencilDecorators } from '../utils';\n\ntype DecoratorsStyleOptionsEnum = 'inline' | 'multiline' | 'ignore';\n\ninterface DecoratorsStyleOptions {\n prop?: DecoratorsStyleOptionsEnum;\n state?: DecoratorsStyleOptionsEnum;\n element?: DecoratorsStyleOptionsEnum;\n event?: DecoratorsStyleOptionsEnum;\n method?: DecoratorsStyleOptionsEnum;\n watch?: DecoratorsStyleOptionsEnum;\n listen?: DecoratorsStyleOptionsEnum;\n}\n\nconst ENUMERATE = ['inline', 'multiline', 'ignore'];\nconst DEFAULTS: DecoratorsStyleOptions = {\n prop: 'ignore',\n state: 'ignore',\n element: 'ignore',\n event: 'ignore',\n method: 'ignore',\n watch: 'ignore',\n listen: 'ignore'\n};\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches Stencil Decorators not used in consistent style.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [\n {\n type: 'object',\n properties: {\n prop: {\n type: 'string',\n enum: ENUMERATE\n },\n state: {\n type: 'string',\n enum: ENUMERATE\n },\n element: {\n type: 'string',\n enum: ENUMERATE\n },\n event: {\n type: 'string',\n enum: ENUMERATE\n },\n method: {\n type: 'string',\n enum: ENUMERATE\n },\n watch: {\n type: 'string',\n enum: ENUMERATE\n },\n listen: {\n type: 'string',\n enum: ENUMERATE\n }\n }\n }],\n type: 'layout'\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n\n const parserServices = context.sourceCode.parserServices;\n const opts = context.options[0] || {};\n const options = { ...DEFAULTS, ...opts };\n\n function checkStyle(decorator: any) {\n const decName = decoratorName(decorator);\n const config = options[decName.toLowerCase()];\n if (!config || config === 'ignore') {\n return;\n }\n const decoratorNode = parserServices.esTreeNodeToTSNodeMap.get(decorator) as ts.Node;\n const decoratorText = decoratorNode.getText()\n .replace('(', '\\\\(')\n .replace(')', '\\\\)');\n const text = decoratorNode.parent.getText();\n const separator = config === 'multiline' ? '\\\\r?\\\\n' : ' ';\n const regExp = new RegExp(`${decoratorText}${separator}`, 'i');\n if (!regExp.test(text)) {\n const node = decorator.parent;\n context.report({\n node: node,\n message: `The @${decName} decorator can only be applied as ${config}.`,\n });\n }\n }\n\n function getStyle(node: any) {\n if (!stencil.isComponent() || !options || !Object.keys(options).length) {\n return;\n }\n const decorators: any[] = getDecorator(node);\n decorators.filter((dec) => stencilDecorators.includes(decoratorName(dec))).forEach(checkStyle);\n }\n\n return {\n ...stencil.rules,\n 'PropertyDefinition': getStyle,\n 'MethodDefinition[kind=method]': getStyle\n };\n }\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport { getDecorator, getType, parseDecorator, stencilComponentContext } from '../utils';\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches Stencil Element type not matching tag name.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [],\n type: 'problem',\n fixable: 'code'\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n\n function parseTag(tag: string) {\n let result = tag[0].toUpperCase() + tag.slice(1);\n const tagBody = tag.split('-');\n if (tagBody.length > 1) {\n result = tagBody.map((tpart) => tpart[0].toUpperCase() + tpart.slice(1)).join('');\n }\n return result;\n }\n\n return {\n ...stencil.rules,\n 'PropertyDefinition > Decorator[expression.callee.name=Element]': (node: any) => {\n if (stencil.isComponent()) {\n const tagType = getType(node.parent);\n const component = getDecorator(node.parent.parent.parent, 'Component');\n const [opts] = parseDecorator(component);\n if (!opts || !opts.tag) {\n return;\n }\n const parsedTag = `HTML${parseTag(opts.tag)}Element`;\n\n if (tagType !== parsedTag) {\n context.report({\n node: node.parent.typeAnnotation ?? node.parent,\n message: `@Element type is not matching tag for component (${parsedTag})`,\n fix(fixer) {\n // If the property has a type annotation, we can replace just that node with the parsed tag\n // @Element() elm: HTMLElement; -> @Element() elm: HTMLSampleTagElement;\n if (node.parent.typeAnnotation?.typeAnnotation) {\n return fixer.replaceText(node.parent.typeAnnotation.typeAnnotation, parsedTag);\n }\n\n // If no type annotation exists on the property, we'll do some string manipulation to insert one.\n // @Element() elm; -> @Element() elm: HTMLSampleTagElement;\n const text = context.sourceCode.getText(node.parent).replace(';', '').concat(`: ${parsedTag};`)\n return fixer.replaceText(node.parent, text);\n }\n });\n }\n }\n }\n };\n }\n};\n\nexport default rule;\n","/**\n * @fileoverview ESLint rules specific to Stencil JS projects.\n * @author Tom Chinery <tom.chinery@addtoevent.co.uk>\n */\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\nimport type { Rule } from 'eslint';\nimport { stencilComponentContext } from '../utils';\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches usage of hostData method.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [],\n type: 'problem'\n },\n\n create(context): Rule.RuleListener {\n\n //----------------------------------------------------------------------\n // Public\n //----------------------------------------------------------------------\n const stencil = stencilComponentContext();\n return {\n ...stencil.rules,\n 'MethodDefinition[key.name=hostData]': (node: any) => {\n if (stencil.isComponent()) {\n context.report({\n node: node.key,\n message: `hostData() is deprecated and <Host> should be used in the render function instead.`\n });\n }\n }\n };\n }\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport ts from 'typescript';\nimport { getDecorator, isPrivate, stencilComponentContext } from '../utils';\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches Stencil Methods marked as private or protected.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [],\n type: 'problem'\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n const parserServices = context.sourceCode.parserServices;\n return {\n ...stencil.rules,\n 'MethodDefinition[kind=method]': (node: any) => {\n if (stencil.isComponent() && getDecorator(node, 'Method')) {\n const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node) as ts.Node;\n if (isPrivate(originalNode)) {\n context.report({\n node: node,\n message: `Class methods decorated with @Method() cannot be private nor protected`\n });\n }\n }\n }\n };\n }\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport ts from 'typescript';\nimport { getDecorator, isPrivate, stencilComponentContext } from '../utils';\n\nconst varsList = new Set<string>();\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches Stencil Watch for not defined variables in Prop or State.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [],\n type: 'suggestion'\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n\n const parserServices = context.sourceCode.parserServices;\n\n function getVars(node: any) {\n if (!stencil.isComponent()) {\n return;\n }\n const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node);\n const varName = originalNode.parent.name.escapedText;\n varsList.add(varName);\n }\n\n function checkWatch(node: any) {\n if (!stencil.isComponent()) {\n return;\n }\n const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node);\n const varName = originalNode.expression.arguments[0].text;\n if (!varsList.has(varName) && !isReservedAttribute(varName.toLowerCase())) {\n context.report({\n node: node,\n message: `Watch decorator @Watch(\"${varName}\") is not matching with any @Prop() or @State()`,\n });\n }\n }\n\n return {\n ClassDeclaration: stencil.rules.ClassDeclaration,\n 'PropertyDefinition > Decorator[expression.callee.name=Prop]': getVars,\n 'MethodDefinition[kind=get] > Decorator[expression.callee.name=Prop]': getVars,\n 'MethodDefinition[kind=set] > Decorator[expression.callee.name=Prop]': getVars,\n 'PropertyDefinition > Decorator[expression.callee.name=State]': getVars, \n 'MethodDefinition[kind=method] > Decorator[expression.callee.name=Watch]': checkWatch,\n 'ClassDeclaration:exit': (node: any) => {\n if (!stencil.isComponent()) {\n return;\n }\n stencil.rules['ClassDeclaration:exit'](node);\n varsList.clear();\n }\n };\n }\n};\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes\nconst GLOBAL_ATTRIBUTES = [\n 'about',\n 'accessKey',\n 'autocapitalize',\n 'autofocus',\n 'class',\n 'contenteditable',\n 'contextmenu',\n 'dir',\n 'draggable',\n 'enterkeyhint',\n 'hidden',\n 'id',\n 'inert',\n 'inputmode',\n 'id',\n 'itemid',\n 'itemprop',\n 'itemref',\n 'itemscope',\n 'itemtype',\n 'lang',\n 'nonce',\n 'part',\n 'popover',\n 'role',\n 'slot',\n 'spellcheck',\n 'style',\n 'tabindex',\n 'title',\n 'translate',\n 'virtualkeyboardpolicy',\n];\n\nconst RESERVED_PUBLIC_ATTRIBUTES = new Set([\n ...GLOBAL_ATTRIBUTES,\n].map(p => p.toLowerCase()));\n\nfunction isReservedAttribute(attributeName: string) {\n return RESERVED_PUBLIC_ATTRIBUTES.has(attributeName.toLowerCase());\n}\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport ts from 'typescript';\nimport {\n getDecoratorList,\n isPrivate,\n stencilComponentContext,\n stencilDecorators,\n stencilLifecycle,\n} from \"../utils\";\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: \"This rule catches own class methods marked as public.\",\n category: \"Possible Errors\",\n recommended: true,\n },\n schema: [],\n type: 'problem',\n fixable: 'code',\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n\n const parserServices = context.sourceCode.parserServices;\n return {\n ...stencil.rules,\n \"MethodDefinition[kind=method]\": (node: any) => {\n if (!stencil.isComponent()) {\n return;\n }\n const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node);\n\n const decorators = getDecoratorList(originalNode);\n const stencilDecorator =\n decorators &&\n decorators.some((dec: any) =>\n stencilDecorators.includes(dec.expression.expression.escapedText)\n );\n const stencilCycle = stencilLifecycle.includes(\n originalNode.name.escapedText\n );\n if (!stencilDecorator && !stencilCycle && !isPrivate(originalNode)) {\n context.report({\n node: node,\n message: `Own class methods cannot be public`,\n fix(fixer) {\n const sourceCode = context.getSourceCode();\n const tokens = sourceCode.getTokens(node);\n const publicToken = tokens.find(token => token.value === 'public');\n\n if (publicToken) {\n return fixer.replaceText(publicToken, 'private');\n } else {\n return fixer.insertTextBefore(node.key, 'private ');\n }\n }\n });\n }\n },\n };\n },\n};\n\nexport default rule;\n","import type { Rule } from \"eslint\";\nimport {\n getDecoratorList,\n isPrivate,\n stencilComponentContext,\n stencilDecorators,\n} from \"../utils\";\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: \"This rule catches own class attributes marked as public.\",\n category: \"Possible Errors\",\n recommended: true,\n },\n schema: [],\n type: 'problem',\n fixable: 'code',\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n\n const parserServices = context.sourceCode.parserServices;\n return {\n ...stencil.rules,\n PropertyDefinition: (node: any) => {\n if (!stencil.isComponent()) {\n return;\n }\n\n const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node);\n const decorators = getDecoratorList(originalNode);\n\n const stencilDecorator =\n decorators &&\n decorators.some((dec: any) =>\n stencilDecorators.includes(dec.expression.expression.escapedText)\n );\n\n if (!stencilDecorator && !isPrivate(originalNode)) {\n context.report({\n node: node,\n message: `Own class properties cannot be public`,\n fix(fixer) {\n const sourceCode = context.getSourceCode();\n const tokens = sourceCode.getTokens(node);\n const publicToken = tokens.find(token => token.value === 'public');\n\n if (publicToken) {\n return fixer.replaceText(publicToken, 'private');\n } else {\n return fixer.insertTextBefore(node.key, 'private ');\n }\n }\n });\n }\n },\n };\n },\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport { getDecorator, parseDecorator, stencilComponentContext } from '../utils';\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches usages of events using @Listen decorator.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [],\n type: 'problem'\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n return {\n ...stencil.rules,\n 'MethodDefinition[kind=method]': (node: any) => {\n if (!stencil.isComponent()) {\n return;\n }\n const listenDec = getDecorator(node, 'Listen');\n if (listenDec) {\n const [eventName, opts] = parseDecorator(listenDec);\n if (typeof eventName === 'string' && opts === undefined) {\n const eventName = listenDec.expression.arguments[0].value;\n if (PREFER_VDOM_LISTENER.includes(eventName)) {\n context.report({\n node: listenDec,\n message: `Use vDOM listener instead.`\n });\n }\n }\n }\n }\n };\n }\n};\n\nconst PREFER_VDOM_LISTENER = [\n 'click',\n 'touchstart',\n 'touchend',\n 'touchmove',\n 'mousedown',\n 'mouseup',\n 'mousemove',\n 'keyup',\n 'keydown',\n 'focusin',\n 'focusout',\n 'focus',\n 'blur'\n];\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport ts from 'typescript';\nimport { getDecorator, isPrivate, stencilComponentContext } from '../utils';\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches Stencil Props marked as private or protected.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [],\n type: 'problem',\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n\n const parserServices = context.sourceCode.parserServices;\n return {\n ...stencil.rules,\n 'PropertyDefinition': (node: any) => {\n if (stencil.isComponent() && getDecorator(node, 'Prop')) {\n const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node) as ts.Node;\n if (isPrivate(originalNode)) {\n context.report({\n node: node,\n message: `Class properties decorated with @Prop() cannot be private nor protected`\n });\n }\n }\n }\n };\n }\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport ts from 'typescript';\nimport { getDecorator, parseDecorator, stencilComponentContext } from '../utils';\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches Stencil Props marked as non readonly.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [],\n type: 'layout',\n fixable: 'code'\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n\n const parserServices = context.sourceCode.parserServices;\n return {\n ...stencil.rules,\n 'PropertyDefinition': (node: any) => {\n const propDecorator = getDecorator(node, 'Prop');\n if (stencil.isComponent() && propDecorator) {\n const [opts] = parseDecorator(propDecorator);\n if (opts && opts.mutable === true) {\n return;\n }\n\n const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node) as ts.Node;\n const hasReadonly = !!(\n ts.canHaveModifiers(originalNode) &&\n ts.getModifiers(originalNode)?.some(m => m.kind === ts.SyntaxKind.ReadonlyKeyword)\n );\n if (!hasReadonly) {\n context.report({\n node: node.key,\n message: `Class properties decorated with @Prop() should be readonly`,\n fix(fixer) {\n return fixer.insertTextBefore(node.key, 'readonly ');\n }\n });\n }\n }\n }\n };\n }\n};\n\nexport default rule;\n","/**\n * @fileoverview ESLint rules specific to Stencil JS projects.\n * @author Tom Chinery <tom.chinery@addtoevent.co.uk>\n */\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\nimport ts from 'typescript';\nimport type { Rule } from 'eslint';\nimport { stencilComponentContext } from '../utils';\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches Stencil Prop names that share names of Global HTML Attributes.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [],\n type: 'problem'\n },\n\n create(context): Rule.RuleListener {\n\n //----------------------------------------------------------------------\n // Public\n //----------------------------------------------------------------------\n\n const stencil = stencilComponentContext();\n const parserServices = context.sourceCode.parserServices;\n const typeChecker = parserServices.program.getTypeChecker() as ts.TypeChecker;\n\n return {\n ...stencil.rules,\n 'MethodDefinition[kind=method][key.name=render] ReturnStatement': (node: any) => {\n if (!stencil.isComponent()) {\n return;\n }\n const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node.argument) as ts.MethodDeclaration;\n const type = typeChecker.getTypeAtLocation(originalNode);\n if (type && type.symbol && type.symbol.escapedName === 'Array') {\n context.report({\n node: node,\n message: `Avoid returning an array in the render() function, use <Host> instead.`\n });\n }\n }\n };\n }\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport { getDecorator, stencilComponentContext } from '../utils';\n\nconst DECORATORS = ['Prop', 'Method', 'Event'];\nconst INVALID_TAGS = ['type', 'memberof'];\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches Stencil Props and Methods using jsdoc.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [],\n type: 'layout'\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n\n const parserServices = context.sourceCode.parserServices;\n\n function getJSDoc(node: any) {\n if (!stencil.isComponent()) {\n return;\n }\n\n DECORATORS.forEach((decName) => {\n if (getDecorator(node, decName)) {\n const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node);\n const jsDoc = originalNode.jsDoc;\n const isValid = jsDoc && jsDoc.length;\n const haveTags = isValid &&\n jsDoc.some((jsdoc: any) => jsdoc.tags && jsdoc.tags.length && jsdoc.tags.some(\n (tag: any) => INVALID_TAGS.includes(tag.tagName.escapedText.toLowerCase())));\n if (!isValid) {\n context.report({\n node: node,\n message: `The @${decName} decorator must be documented.`\n });\n } else if (haveTags) {\n context.report({\n node: node,\n message: `The @${decName} decorator have not valid tags (${INVALID_TAGS.join(', ')}).`\n });\n }\n }\n });\n }\n\n return {\n ...stencil.rules,\n 'PropertyDefinition': getJSDoc,\n 'MethodDefinition[kind=method]': getJSDoc\n };\n }\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport { getDecorator, parseDecorator, stencilComponentContext } from '../utils';\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches required prefix in component tag name.',\n category: 'Possible Errors',\n recommended: false\n },\n schema: [\n {\n type: 'array',\n minLength: 1,\n additionalProperties: false\n }\n ],\n type: 'layout'\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n\n return {\n ...stencil.rules,\n 'ClassDeclaration': (node: any) => {\n const component = getDecorator(node, 'Component');\n if (!component) {\n return;\n }\n const [{ tag }] = parseDecorator(component);\n const options = context.options[0];\n const match = options.some((t: string) => tag.startsWith(t));\n\n if (!match) {\n context.report({\n node: node,\n message: `The component with tagName ${tag} have not a valid prefix.`\n });\n }\n }\n };\n }\n};\n\nexport default rule;\n","/**\n * @fileoverview ESLint rules specific to Stencil JS projects.\n * @author Tom Chinery <tom.chinery@addtoevent.co.uk>\n */\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\nimport type { Rule } from 'eslint';\nimport { stencilComponentContext } from '../utils';\nimport { JSDOM } from 'jsdom';\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches Stencil Prop names that share names of Global HTML Attributes.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [],\n type: 'problem'\n },\n\n create(context): Rule.RuleListener {\n\n //----------------------------------------------------------------------\n // Public\n //----------------------------------------------------------------------\n const stencil = stencilComponentContext();\n\n const checkName = (node: any) => {\n if (!stencil.isComponent()) {\n return;\n }\n const decoratorName = node.expression.callee.name;\n if (decoratorName === 'Prop' || decoratorName === 'Method') {\n const propName = node.parent.key.name;\n if (isReservedMember(propName)) {\n context.report({\n node: node.parent.key,\n message: `The @${decoratorName} name \"${propName} conflicts with a key in the HTMLElement prototype. Please choose a different name.`\n });\n }\n if (propName.startsWith('data-')) {\n context.report({\n node: node.parent.key,\n message: 'Avoid using Global HTML Attributes as Prop names.'\n });\n }\n }\n };\n return {\n ...stencil.rules,\n 'PropertyDefinition > Decorator[expression.callee.name=Prop]': checkName,\n 'MethodDefinition[kind=method] > Decorator[expression.callee.name=Method]': checkName\n };\n }\n};\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes\nconst GLOBAL_ATTRIBUTES = [\n 'about',\n 'accessKey',\n 'autocapitalize',\n 'autofocus',\n 'class',\n 'contenteditable',\n 'contextmenu',\n 'dir',\n 'draggable',\n 'enterkeyhint',\n 'hidden',\n 'id',\n 'inert',\n 'inputmode',\n 'id',\n 'itemid',\n 'itemprop',\n 'itemref',\n 'itemscope',\n 'itemtype',\n 'lang',\n 'nonce',\n 'part',\n 'popover',\n 'role',\n 'slot',\n 'spellcheck',\n 'style',\n 'tabindex',\n 'title',\n 'translate',\n 'virtualkeyboardpolicy',\n];\n\nconst JSX_KEYS = [\n 'ref',\n 'key'\n];\n\nfunction getHtmlElementProperties(): string[] {\n const { window: win } = new JSDOM();\n const { document: doc } = win;\n const htmlElement = doc.createElement(\"tester-component\"); // creates a custom element base (HTMLElement)\n const relevantInterfaces = [win.HTMLElement, win.Element, win.Node, win.EventTarget];\n const props = new Set<string>();\n\n let currentInstance = htmlElement;\n while (currentInstance && relevantInterfaces.some(relevantInterface => currentInstance instanceof relevantInterface)) {\n Object.getOwnPropertyNames(currentInstance).forEach((prop: string) => props.add(prop));\n currentInstance = Object.getPrototypeOf(currentInstance);\n }\n\n return Array.from(props);\n}\n\nconst RESERVED_PUBLIC_MEMBERS = new Set([\n ...GLOBAL_ATTRIBUTES,\n ...getHtmlElementProperties(),\n ...JSX_KEYS\n].map(p => p.toLowerCase()));\n\nfunction isReservedMember(memberName: string) {\n return RESERVED_PUBLIC_MEMBERS.has(memberName.toLowerCase());\n}\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport ts from 'typescript';\nimport { getDecorator } from '../utils';\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches modules that expose more than just the Stencil Component itself.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [],\n type: 'problem'\n },\n\n create(context): Rule.RuleListener {\n const parserServices = context.sourceCode.parserServices;\n const typeChecker = parserServices.program.getTypeChecker() as ts.TypeChecker;\n return {\n 'ClassDeclaration': (node: any) => {\n const component = getDecorator(node, 'Component');\n if (component) {\n const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node);\n const nonTypeExports = typeChecker.getExportsOfModule(\n typeChecker.getSymbolAtLocation(originalNode.getSourceFile())!)\n .filter(symbol => (symbol.flags & (ts.SymbolFlags.Interface | ts.SymbolFlags.TypeAlias)) === 0)\n .filter(symbol => symbol.name !== originalNode.name.text);\n\n nonTypeExports.forEach(symbol => {\n const errorNode = (symbol.valueDeclaration)\n ? parserServices.tsNodeToESTreeNodeMap.get(symbol.valueDeclaration).id\n : parserServices.tsNodeToESTreeNodeMap.get(symbol.declarations?.[0]);\n\n context.report({\n node: errorNode,\n message: `To allow efficient bundling, modules using @Component() can only have a single export which is the component class itself. Any other exports should be moved to a separate file. For further information check out: https://stenciljs.com/docs/module-bundling`\n });\n });\n }\n }\n };\n }\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport { parseDecorator, stencilComponentContext } from '../utils';\n\nconst mutableProps = new Map<string, any>();\nconst mutableAssigned = new Set<string>();\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches mutable Props that not need to be mutable.',\n category: 'Possible Errors',\n recommended: true\n },\n schema: [],\n type: 'layout',\n },\n\n create(context): Rule.RuleListener {\n const stencil = stencilComponentContext();\n\n function getMutable(node: any) {\n if (!stencil.isComponent()) {\n return;\n }\n const parsed = parseDecorator(node);\n const mutable = parsed && parsed.length && parsed[0].mutable || false;\n\n if (mutable) {\n const varName = node.parent.key.name;\n mutableProps.set(varName, node);\n }\n }\n\n function checkAssigment(node: any) {\n if (!stencil.isComponent()) {\n return;\n }\n const propName = node.left.property.name;\n mutableAssigned.add(propName);\n }\n\n stencil.rules[\"ClassDeclaration:exit\"]\n return {\n 'ClassDeclaration': stencil.rules.ClassDeclaration,\n 'PropertyDefinition > Decorator[expression.callee.name=Prop]': getMutable,\n 'AssignmentExpression[left.object.type=ThisExpression][left.property.type=Identifier]': checkAssigment,\n 'ClassDeclaration:exit': (node: any) => {\n const isCmp = stencil.isComponent();\n stencil.rules[\"ClassDeclaration:exit\"](node);\n\n if (isCmp) {\n mutableAssigned.forEach((propName) => mutableProps.delete(propName));\n mutableProps.forEach((varNode, varName) => {\n context.report({\n node: varNode.parent,\n message: `@Prop() \"${varName}\" should not be mutable`,\n });\n });\n\n mutableAssigned.clear();\n mutableProps.clear();\n }\n }\n };\n }\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\n\nconst rule: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'This rule catches function calls at the top level',\n category: 'Possible Errors',\n recommended: false\n },\n schema: [\n {\n type: 'array',\n items: {\n type: 'string'\n },\n minLength: 0,\n additionalProperties: false\n }\n ],\n type: 'suggestion'\n },\n\n create