@stencil/eslint-plugin
Version:
ESLint rules specific to Stencil JS projects.
1,604 lines (1,575 loc) • 54.8 kB
JavaScript
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) {
__defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
}
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let eslint_plugin_react = require("eslint-plugin-react");
eslint_plugin_react = __toESM(eslint_plugin_react);
let typescript = require("typescript");
typescript = __toESM(typescript);
let eslint_utils = require("eslint-utils");
let tsutils = require("tsutils");
let jsdom = require("jsdom");
//#region src/utils.ts
const SyntaxKind = typescript.default.SyntaxKind;
/**
* Checks if an ESTree node has private or protected accessibility.
*/
function isPrivateESTree(node) {
if (node.accessibility === "private" || node.accessibility === "protected") return true;
return node.key?.type === "PrivateIdentifier";
}
/**
* Checks if an ESTree node has any Stencil decorator.
*/
function hasStencilDecorator(node) {
return getDecorator(node).some((dec) => stencilDecorators.includes(decoratorName(dec)));
}
/**
* Returns the JSDoc block comments attached to a node using ESTree sourceCode APIs.
* Each returned object has `value` (the raw comment text) and parsed `tags`.
*/
function getJSDocComments(node, sourceCode) {
let comments = sourceCode.getCommentsBefore(node);
if ((!comments || comments.length === 0) && node.decorators && node.decorators.length > 0) comments = sourceCode.getCommentsBefore(node.decorators[0]);
return comments.filter((c) => c.type === "Block" && c.value.startsWith("*")).map((c) => {
const tags = [];
const tagRegex = /@(\w+)\s*(.*)/g;
let match;
while ((match = tagRegex.exec(c.value)) !== null) tags.push({
tagName: match[1],
comment: match[2].trim()
});
return {
value: c.value,
tags
};
});
}
function getDecorator(node, decoratorName) {
if (decoratorName) return node.decorators && node.decorators.find(isDecoratorNamed(decoratorName));
return node.decorators ? node.decorators.filter((dec) => dec.expression) : [];
}
function parseDecorator(decorator) {
if (decorator && decorator.expression && decorator.expression.type === "CallExpression") return decorator.expression.arguments.map((a) => {
const parsed = (0, eslint_utils.getStaticValue)(a);
return parsed ? parsed.value : void 0;
});
return [];
}
function decoratorName(dec) {
return dec.expression && dec.expression.callee.name;
}
function isDecoratorNamed(propName) {
return (dec) => {
return decoratorName(dec) === propName;
};
}
function stencilComponentContext() {
let componentNode;
return {
rules: {
"ClassDeclaration": (node) => {
const component = getDecorator(node, "Component");
if (component) componentNode = component;
},
"ClassDeclaration:exit": (node) => {
if (componentNode === node) componentNode = void 0;
}
},
isComponent() {
return !!componentNode;
}
};
}
function getType(node) {
return node.typeAnnotation?.typeAnnotation?.typeName?.name;
}
const stencilDecorators = [
"Component",
"Prop",
"State",
"Watch",
"Element",
"Method",
"Event",
"Listen",
"AttachInternals"
];
const stencilLifecycle = [
"connectedCallback",
"disconnectedCallback",
"componentWillLoad",
"componentDidLoad",
"componentWillRender",
"componentDidRender",
"componentShouldUpdate",
"componentWillUpdate",
"componentDidUpdate",
"formAssociatedCallback",
"formDisabledCallback",
"formResetCallback",
"formStateRestoreCallback",
"render"
];
const TOKEN_TO_TEXT = {
[SyntaxKind.OpenBraceToken]: "{",
[SyntaxKind.CloseBraceToken]: "}",
[SyntaxKind.OpenParenToken]: "(",
[SyntaxKind.CloseParenToken]: ")",
[SyntaxKind.OpenBracketToken]: "[",
[SyntaxKind.CloseBracketToken]: "]",
[SyntaxKind.DotToken]: ".",
[SyntaxKind.DotDotDotToken]: "...",
[SyntaxKind.SemicolonToken]: ",",
[SyntaxKind.CommaToken]: ",",
[SyntaxKind.LessThanToken]: "<",
[SyntaxKind.GreaterThanToken]: ">",
[SyntaxKind.LessThanEqualsToken]: "<=",
[SyntaxKind.GreaterThanEqualsToken]: ">=",
[SyntaxKind.EqualsEqualsToken]: "==",
[SyntaxKind.ExclamationEqualsToken]: "!=",
[SyntaxKind.EqualsEqualsEqualsToken]: "===",
[SyntaxKind.InstanceOfKeyword]: "instanceof",
[SyntaxKind.ExclamationEqualsEqualsToken]: "!==",
[SyntaxKind.EqualsGreaterThanToken]: "=>",
[SyntaxKind.PlusToken]: "+",
[SyntaxKind.MinusToken]: "-",
[SyntaxKind.AsteriskToken]: "*",
[SyntaxKind.AsteriskAsteriskToken]: "**",
[SyntaxKind.SlashToken]: "/",
[SyntaxKind.PercentToken]: "%",
[SyntaxKind.PlusPlusToken]: "++",
[SyntaxKind.MinusMinusToken]: "--",
[SyntaxKind.LessThanLessThanToken]: "<<",
[SyntaxKind.LessThanSlashToken]: "</",
[SyntaxKind.GreaterThanGreaterThanToken]: ">>",
[SyntaxKind.GreaterThanGreaterThanGreaterThanToken]: ">>>",
[SyntaxKind.AmpersandToken]: "&",
[SyntaxKind.BarToken]: "|",
[SyntaxKind.CaretToken]: "^",
[SyntaxKind.ExclamationToken]: "!",
[SyntaxKind.TildeToken]: "~",
[SyntaxKind.AmpersandAmpersandToken]: "&&",
[SyntaxKind.BarBarToken]: "||",
[SyntaxKind.QuestionToken]: "?",
[SyntaxKind.ColonToken]: ":",
[SyntaxKind.EqualsToken]: "=",
[SyntaxKind.PlusEqualsToken]: "+=",
[SyntaxKind.MinusEqualsToken]: "-=",
[SyntaxKind.AsteriskEqualsToken]: "*=",
[SyntaxKind.AsteriskAsteriskEqualsToken]: "**=",
[SyntaxKind.SlashEqualsToken]: "/=",
[SyntaxKind.PercentEqualsToken]: "%=",
[SyntaxKind.LessThanLessThanEqualsToken]: "<<=",
[SyntaxKind.GreaterThanGreaterThanEqualsToken]: ">>=",
[SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken]: ">>>=",
[SyntaxKind.AmpersandEqualsToken]: "&=",
[SyntaxKind.BarEqualsToken]: "|=",
[SyntaxKind.CaretEqualsToken]: "^=",
[SyntaxKind.AtToken]: "@",
[SyntaxKind.InKeyword]: "in",
[SyntaxKind.UniqueKeyword]: "unique",
[SyntaxKind.KeyOfKeyword]: "keyof",
[SyntaxKind.NewKeyword]: "new",
[SyntaxKind.ImportKeyword]: "import",
[SyntaxKind.ReadonlyKeyword]: "readonly"
};
//#endregion
//#region src/rules/async-methods.ts
const rule$25 = {
meta: {
docs: {
description: "This rule catches Stencil public methods that are not async.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "problem",
fixable: "code"
},
create(context) {
const stencil = stencilComponentContext();
const parserServices = context.sourceCode.parserServices;
if (!parserServices?.esTreeNodeToTSNodeMap || !parserServices?.program) return { ...stencil.rules };
const typeChecker = parserServices.program.getTypeChecker();
return {
...stencil.rules,
"MethodDefinition > Decorator[expression.callee.name=Method]": (decoratorNode) => {
if (!stencil.isComponent()) return;
const node = decoratorNode.parent;
const method = parserServices.esTreeNodeToTSNodeMap.get(node);
const signature = typeChecker.getSignatureFromDeclaration(method);
if (!(0, tsutils.isThenableType)(typeChecker, method, typeChecker.getReturnTypeOfSignature(signature))) {
const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node);
const text = String(originalNode.getFullText());
context.report({
node: node.key,
message: `External @Method() ${node.key.name}() must return a Promise. Consider prefixing the method with async, such as @Method() async ${node.key.name}().`,
fix(fixer) {
const result = text.trimLeft().replace(/@Method\(\)\n(\s*)/, "@Method()\n$1async ").replace("@Method() ", "@Method() async").replace("async public", "public async").replace("async private", "private async");
return fixer.replaceText(node, result);
}
});
}
}
};
}
};
//#endregion
//#region src/rules/ban-default-true.ts
const rule$24 = {
meta: {
docs: {
description: "This rule catches Stencil Props defaulting to true.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "problem"
},
create(context) {
const stencil = stencilComponentContext();
return {
...stencil.rules,
"PropertyDefinition": (node) => {
const propDecorator = getDecorator(node, "Prop");
if (!(stencil.isComponent() && propDecorator)) return;
if (node.value?.value === true) context.report({
node,
message: `Boolean properties decorated with @Prop() should default to false`
});
}
};
}
};
//#endregion
//#region src/rules/ban-prefix.ts
const DEFAULTS$2 = [
"stencil",
"stnl",
"st"
];
const rule$23 = {
meta: {
docs: {
description: "This rule catches usages banned prefix in component tag name.",
category: "Possible Errors",
recommended: true
},
schema: [{
type: "array",
items: { type: "string" },
minLength: 1,
additionalProperties: false
}],
type: "problem"
},
create(context) {
return {
...stencilComponentContext().rules,
"ClassDeclaration": (node) => {
const component = getDecorator(node, "Component");
if (!component) return;
const [opts] = parseDecorator(component);
if (!opts || !opts.tag) return;
const tag = opts.tag;
if ((context.options[0] || DEFAULTS$2).some((t) => tag.startsWith(`${t}-`))) context.report({
node,
message: `The component with tag name ${tag} have a banned prefix.`
});
}
};
}
};
//#endregion
//#region src/rules/class-pattern.ts
const rule$22 = {
meta: {
docs: {
description: "This rule catches usages of non valid class names.",
category: "Possible Errors",
recommended: false
},
schema: [{
type: "object",
properties: {
pattern: { type: "string" },
ignoreCase: { type: "boolean" }
},
additionalProperties: false
}],
type: "problem"
},
create(context) {
return {
...stencilComponentContext().rules,
"ClassDeclaration": (node) => {
const component = getDecorator(node, "Component");
const options = context.options[0];
const { pattern, ignoreCase } = options || {};
if (!component || !options || !pattern) return;
const className = node.id?.name;
if (!className) return;
const regExp = new RegExp(pattern, ignoreCase ? "i" : void 0);
if (!regExp.test(className)) {
const [opts] = parseDecorator(component);
if (!opts || !opts.tag) return;
context.report({
node,
message: `The class name in component with tag name ${opts.tag} is not valid (${regExp}).`
});
}
}
};
}
};
//#endregion
//#region src/rules/decorators-context.ts
const rule$21 = {
meta: {
docs: {
description: "This rule catches Stencil Decorators used in incorrect locations.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "problem"
},
create(context) {
const stencil = stencilComponentContext();
return {
...stencil.rules,
"Decorator": (node) => {
if (!stencil.isComponent()) return;
if (node.expression && node.expression.callee) {
const decName = node.expression.callee.name;
if (decName === "Prop" || decName === "State" || decName === "Element" || decName === "Event") {
if (node.parent.type !== "PropertyDefinition" && node.parent.type === "MethodDefinition" && ["get", "set"].indexOf(node.parent.kind) < 0) context.report({
node,
message: `The @${decName} decorator can only be applied to class properties.`
});
} else if (decName === "Method" || decName === "Watch" || decName === "Listen") {
if (node.parent.type !== "MethodDefinition") context.report({
node,
message: `The @${decName} decorator can only be applied to class methods.`
});
} else if (decName === "Component") {
if (node.parent.type !== "ClassDeclaration") context.report({
node,
message: `The @${decName} decorator can only be applied to a class.`
});
}
}
}
};
}
};
//#endregion
//#region src/rules/decorators-style.ts
const ENUMERATE = [
"inline",
"multiline",
"ignore"
];
const DEFAULTS$1 = {
prop: "ignore",
state: "ignore",
element: "ignore",
event: "ignore",
method: "ignore",
watch: "ignore",
listen: "ignore"
};
const rule$20 = {
meta: {
docs: {
description: "This rule catches Stencil Decorators not used in consistent style.",
category: "Possible Errors",
recommended: true
},
schema: [{
type: "object",
properties: {
prop: {
type: "string",
enum: ENUMERATE
},
state: {
type: "string",
enum: ENUMERATE
},
element: {
type: "string",
enum: ENUMERATE
},
event: {
type: "string",
enum: ENUMERATE
},
method: {
type: "string",
enum: ENUMERATE
},
watch: {
type: "string",
enum: ENUMERATE
},
listen: {
type: "string",
enum: ENUMERATE
}
}
}],
type: "layout"
},
create(context) {
const stencil = stencilComponentContext();
const opts = context.options[0] || {};
const options = {
...DEFAULTS$1,
...opts
};
function checkStyle(decorator) {
const decName = decoratorName(decorator);
const config = options[decName.toLowerCase()];
if (!config || config === "ignore") return;
const node = decorator.parent;
const decoratorEndLine = decorator.loc.end.line;
const memberStartLine = node.key.loc.start.line;
const isOnSameLineAsMember = decoratorEndLine === memberStartLine;
if (config === "multiline") {
const allDecorators = node.decorators || [];
const indexInAll = allDecorators.indexOf(decorator);
const nextDecorator = indexInAll >= 0 ? allDecorators[indexInAll + 1] : void 0;
if (decoratorEndLine === (nextDecorator ? nextDecorator.loc.start.line : memberStartLine)) context.report({
node,
message: `The @${decName} decorator can only be applied as multiline.`
});
} else if (config === "inline" && !isOnSameLineAsMember) context.report({
node,
message: `The @${decName} decorator can only be applied as inline.`
});
}
function getStyle(node) {
if (!stencil.isComponent() || !options || !Object.keys(options).length) return;
getDecorator(node).filter((dec) => stencilDecorators.includes(decoratorName(dec))).forEach(checkStyle);
}
return {
...stencil.rules,
"PropertyDefinition": getStyle,
"MethodDefinition[kind=method]": getStyle
};
}
};
//#endregion
//#region src/rules/element-type.ts
const rule$19 = {
meta: {
docs: {
description: "This rule catches Stencil Element type not matching tag name.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "problem",
fixable: "code"
},
create(context) {
const stencil = stencilComponentContext();
function parseTag(tag) {
let result = tag[0].toUpperCase() + tag.slice(1);
const tagBody = tag.split("-");
if (tagBody.length > 1) result = tagBody.map((tpart) => tpart[0].toUpperCase() + tpart.slice(1)).join("");
return result;
}
return {
...stencil.rules,
"PropertyDefinition > Decorator[expression.callee.name=Element]": (node) => {
if (stencil.isComponent()) {
const tagType = getType(node.parent);
const [opts] = parseDecorator(getDecorator(node.parent.parent.parent, "Component"));
if (!opts || !opts.tag) return;
const parsedTag = `HTML${parseTag(opts.tag)}Element`;
if (tagType !== parsedTag) context.report({
node: node.parent.typeAnnotation ?? node.parent,
message: `@Element type is not matching tag for component (${parsedTag})`,
fix(fixer) {
if (node.parent.typeAnnotation?.typeAnnotation) return fixer.replaceText(node.parent.typeAnnotation.typeAnnotation, parsedTag);
const text = context.sourceCode.getText(node.parent).replace(";", "").concat(`: ${parsedTag};`);
return fixer.replaceText(node.parent, text);
}
});
}
}
};
}
};
//#endregion
//#region src/rules/host-data-deprecated.ts
const rule$18 = {
meta: {
docs: {
description: "This rule catches usage of hostData method.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "problem"
},
create(context) {
const stencil = stencilComponentContext();
return {
...stencil.rules,
"MethodDefinition[key.name=hostData]": (node) => {
if (stencil.isComponent()) context.report({
node: node.key,
message: `hostData() is deprecated and <Host> should be used in the render function instead.`
});
}
};
}
};
//#endregion
//#region src/rules/methods-must-be-public.ts
const rule$17 = {
meta: {
docs: {
description: "This rule catches Stencil Methods marked as private or protected.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "problem"
},
create(context) {
const stencil = stencilComponentContext();
return {
...stencil.rules,
"MethodDefinition[kind=method]": (node) => {
if (stencil.isComponent() && getDecorator(node, "Method")) {
if (isPrivateESTree(node)) context.report({
node,
message: `Class methods decorated with @Method() cannot be private nor protected`
});
}
}
};
}
};
//#endregion
//#region src/rules/no-unused-watch.ts
const varsList = /* @__PURE__ */ new Set();
const rule$16 = {
meta: {
docs: {
description: "This rule catches Stencil Watch for not defined variables in Prop or State.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "suggestion"
},
create(context) {
const stencil = stencilComponentContext();
function getVars(node) {
if (!stencil.isComponent()) return;
const memberNode = node.parent;
const varName = memberNode.key?.name || memberNode.key?.value;
if (varName) varsList.add(varName);
}
function checkWatch(node) {
if (!stencil.isComponent()) return;
const varName = node.expression?.arguments?.[0]?.value;
if (varName && !varsList.has(varName) && !isReservedAttribute(varName.toLowerCase())) context.report({
node,
message: `Watch decorator @Watch("${varName}") is not matching with any @Prop() or @State()`
});
}
return {
ClassDeclaration: stencil.rules.ClassDeclaration,
"PropertyDefinition > Decorator[expression.callee.name=Prop]": getVars,
"MethodDefinition[kind=get] > Decorator[expression.callee.name=Prop]": getVars,
"MethodDefinition[kind=set] > Decorator[expression.callee.name=Prop]": getVars,
"PropertyDefinition > Decorator[expression.callee.name=State]": getVars,
"MethodDefinition[kind=method] > Decorator[expression.callee.name=Watch]": checkWatch,
"ClassDeclaration:exit": (node) => {
if (!stencil.isComponent()) return;
stencil.rules["ClassDeclaration:exit"](node);
varsList.clear();
}
};
}
};
const RESERVED_PUBLIC_ATTRIBUTES = new Set([...[
"about",
"accessKey",
"autocapitalize",
"autofocus",
"class",
"contenteditable",
"contextmenu",
"dir",
"draggable",
"enterkeyhint",
"hidden",
"id",
"inert",
"inputmode",
"id",
"itemid",
"itemprop",
"itemref",
"itemscope",
"itemtype",
"lang",
"nonce",
"part",
"popover",
"role",
"slot",
"spellcheck",
"style",
"tabindex",
"title",
"translate",
"virtualkeyboardpolicy"
]].map((p) => p.toLowerCase()));
function isReservedAttribute(attributeName) {
return RESERVED_PUBLIC_ATTRIBUTES.has(attributeName.toLowerCase());
}
//#endregion
//#region src/rules/own-methods-must-be-private.ts
const rule$15 = {
meta: {
docs: {
description: "This rule catches own class methods marked as public.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "problem",
fixable: "code"
},
create(context) {
const stencil = stencilComponentContext();
return {
...stencil.rules,
"MethodDefinition[kind=method]": (node) => {
if (!stencil.isComponent()) return;
const methodName = node.key.name || node.key.value;
const isStencilCycle = stencilLifecycle.includes(methodName);
if (!hasStencilDecorator(node) && !isStencilCycle && !isPrivateESTree(node)) context.report({
node,
message: `Own class methods cannot be public`,
fix(fixer) {
const publicToken = context.sourceCode.getTokens(node).find((token) => token.value === "public");
if (publicToken) return fixer.replaceText(publicToken, "private");
else return fixer.insertTextBefore(node.key, "private ");
}
});
}
};
}
};
//#endregion
//#region src/rules/own-props-must-be-private.ts
const rule$14 = {
meta: {
docs: {
description: "This rule catches own class attributes marked as public.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "problem",
fixable: "code"
},
create(context) {
const stencil = stencilComponentContext();
return {
...stencil.rules,
PropertyDefinition: (node) => {
if (!stencil.isComponent()) return;
if (!hasStencilDecorator(node) && !isPrivateESTree(node)) context.report({
node,
message: `Own class properties cannot be public`,
fix(fixer) {
const publicToken = context.sourceCode.getTokens(node).find((token) => token.value === "public");
if (publicToken) return fixer.replaceText(publicToken, "private");
else return fixer.insertTextBefore(node.key, "private ");
}
});
}
};
}
};
//#endregion
//#region src/rules/prefer-vdom-listener.ts
const rule$13 = {
meta: {
docs: {
description: "This rule catches usages of events using @Listen decorator.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "problem"
},
create(context) {
const stencil = stencilComponentContext();
return {
...stencil.rules,
"MethodDefinition[kind=method]": (node) => {
if (!stencil.isComponent()) return;
const listenDec = getDecorator(node, "Listen");
if (listenDec) {
const [eventName, opts] = parseDecorator(listenDec);
if (typeof eventName === "string" && opts === void 0) {
const eventName = listenDec.expression.arguments[0].value;
if (PREFER_VDOM_LISTENER.includes(eventName)) context.report({
node: listenDec,
message: `Use vDOM listener instead.`
});
}
}
}
};
}
};
const PREFER_VDOM_LISTENER = [
"click",
"touchstart",
"touchend",
"touchmove",
"mousedown",
"mouseup",
"mousemove",
"keyup",
"keydown",
"focusin",
"focusout",
"focus",
"blur"
];
//#endregion
//#region src/rules/props-must-be-public.ts
const rule$12 = {
meta: {
docs: {
description: "This rule catches Stencil Props marked as private or protected.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "problem"
},
create(context) {
const stencil = stencilComponentContext();
return {
...stencil.rules,
"PropertyDefinition": (node) => {
if (stencil.isComponent() && getDecorator(node, "Prop")) {
if (isPrivateESTree(node)) context.report({
node,
message: `Class properties decorated with @Prop() cannot be private nor protected`
});
}
}
};
}
};
//#endregion
//#region src/rules/props-must-be-readonly.ts
const rule$11 = {
meta: {
docs: {
description: "This rule catches Stencil Props marked as non readonly.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "layout",
fixable: "code"
},
create(context) {
const stencil = stencilComponentContext();
return {
...stencil.rules,
"PropertyDefinition": (node) => {
const propDecorator = getDecorator(node, "Prop");
if (stencil.isComponent() && propDecorator) {
const [opts] = parseDecorator(propDecorator);
if (opts && opts.mutable === true) return;
if (!node.readonly) context.report({
node: node.key,
message: `Class properties decorated with @Prop() should be readonly`,
fix(fixer) {
return fixer.insertTextBefore(node.key, "readonly ");
}
});
}
}
};
}
};
//#endregion
//#region src/rules/render-returns-host.ts
const rule$10 = {
meta: {
docs: {
description: "This rule catches Stencil Prop names that share names of Global HTML Attributes.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "problem"
},
create(context) {
const stencil = stencilComponentContext();
const parserServices = context.sourceCode.parserServices;
if (!parserServices?.esTreeNodeToTSNodeMap || !parserServices?.program) return { ...stencil.rules };
const typeChecker = parserServices.program.getTypeChecker();
return {
...stencil.rules,
"MethodDefinition[kind=method][key.name=render] ReturnStatement": (node) => {
if (!stencil.isComponent()) return;
const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node.argument);
const type = typeChecker.getTypeAtLocation(originalNode);
if (type && type.symbol && type.symbol.escapedName === "Array") context.report({
node,
message: `Avoid returning an array in the render() function, use <Host> instead.`
});
}
};
}
};
//#endregion
//#region src/rules/required-jsdoc.ts
const DECORATORS = [
"Prop",
"Method",
"Event"
];
const INVALID_TAGS = ["type", "memberof"];
const rule$9 = {
meta: {
docs: {
description: "This rule catches Stencil Props and Methods using jsdoc.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "layout"
},
create(context) {
const stencil = stencilComponentContext();
function getJSDoc(node) {
if (!stencil.isComponent()) return;
DECORATORS.forEach((decName) => {
if (getDecorator(node, decName)) {
const jsDocs = getJSDocComments(node, context.sourceCode);
const isValid = jsDocs.length > 0;
const haveTags = isValid && jsDocs.some((jsdoc) => jsdoc.tags.length > 0 && jsdoc.tags.some((tag) => INVALID_TAGS.includes(tag.tagName.toLowerCase())));
if (!isValid) context.report({
node,
message: `The @${decName} decorator must be documented.`
});
else if (haveTags) context.report({
node,
message: `The @${decName} decorator have not valid tags (${INVALID_TAGS.join(", ")}).`
});
}
});
}
return {
...stencil.rules,
"PropertyDefinition": getJSDoc,
"MethodDefinition[kind=method]": getJSDoc
};
}
};
//#endregion
//#region src/rules/required-prefix.ts
const rule$8 = {
meta: {
docs: {
description: "This rule catches required prefix in component tag name.",
category: "Possible Errors",
recommended: false
},
schema: [{
type: "array",
minLength: 1,
additionalProperties: false
}],
type: "layout"
},
create(context) {
return {
...stencilComponentContext().rules,
"ClassDeclaration": (node) => {
const component = getDecorator(node, "Component");
if (!component) return;
const [{ tag }] = parseDecorator(component);
if (!context.options[0].some((t) => tag.startsWith(t))) context.report({
node,
message: `The component with tagName ${tag} have not a valid prefix.`
});
}
};
}
};
//#endregion
//#region src/rules/reserved-member-names.ts
const rule$7 = {
meta: {
docs: {
description: "This rule catches Stencil Prop names that share names of Global HTML Attributes.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "problem"
},
create(context) {
const stencil = stencilComponentContext();
const checkName = (node) => {
if (!stencil.isComponent()) return;
const decoratorName = node.expression.callee.name;
if (decoratorName === "Prop" || decoratorName === "Method") {
const propName = node.parent.key.name;
if (isReservedMember(propName)) context.report({
node: node.parent.key,
message: `The @${decoratorName} name "${propName} conflicts with a key in the HTMLElement prototype. Please choose a different name.`
});
if (propName.startsWith("data-")) context.report({
node: node.parent.key,
message: "Avoid using Global HTML Attributes as Prop names."
});
}
};
return {
...stencil.rules,
"PropertyDefinition > Decorator[expression.callee.name=Prop]": checkName,
"MethodDefinition[kind=method] > Decorator[expression.callee.name=Method]": checkName
};
}
};
const GLOBAL_ATTRIBUTES = [
"about",
"accessKey",
"autocapitalize",
"autofocus",
"class",
"contenteditable",
"contextmenu",
"dir",
"draggable",
"enterkeyhint",
"hidden",
"id",
"inert",
"inputmode",
"id",
"itemid",
"itemprop",
"itemref",
"itemscope",
"itemtype",
"lang",
"nonce",
"part",
"popover",
"role",
"slot",
"spellcheck",
"style",
"tabindex",
"title",
"translate",
"virtualkeyboardpolicy"
];
const JSX_KEYS = ["ref", "key"];
function getHtmlElementProperties() {
const { window: win } = new jsdom.JSDOM();
const { document: doc } = win;
const htmlElement = doc.createElement("tester-component");
const relevantInterfaces = [
win.HTMLElement,
win.Element,
win.Node,
win.EventTarget
];
const props = /* @__PURE__ */ new Set();
let currentInstance = htmlElement;
while (currentInstance && relevantInterfaces.some((relevantInterface) => currentInstance instanceof relevantInterface)) {
Object.getOwnPropertyNames(currentInstance).forEach((prop) => props.add(prop));
currentInstance = Object.getPrototypeOf(currentInstance);
}
return Array.from(props);
}
const RESERVED_PUBLIC_MEMBERS = new Set([
...GLOBAL_ATTRIBUTES,
...getHtmlElementProperties(),
...JSX_KEYS
].map((p) => p.toLowerCase()));
function isReservedMember(memberName) {
return RESERVED_PUBLIC_MEMBERS.has(memberName.toLowerCase());
}
//#endregion
//#region src/rules/single-export.ts
const rule$6 = {
meta: {
docs: {
description: "This rule catches modules that expose more than just the Stencil Component itself.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "problem"
},
create(context) {
const parserServices = context.sourceCode.parserServices;
if (!parserServices?.esTreeNodeToTSNodeMap || !parserServices?.program) return {};
const typeChecker = parserServices.program.getTypeChecker();
return { "ClassDeclaration": (node) => {
if (getDecorator(node, "Component")) {
const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node);
typeChecker.getExportsOfModule(typeChecker.getSymbolAtLocation(originalNode.getSourceFile())).filter((symbol) => (symbol.flags & (typescript.default.SymbolFlags.Interface | typescript.default.SymbolFlags.TypeAlias)) === 0).filter((symbol) => symbol.name !== originalNode.name.text).forEach((symbol) => {
const errorNode = symbol.valueDeclaration ? parserServices.tsNodeToESTreeNodeMap.get(symbol.valueDeclaration).id : parserServices.tsNodeToESTreeNodeMap.get(symbol.declarations?.[0]);
context.report({
node: errorNode,
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`
});
});
}
} };
}
};
//#endregion
//#region src/rules/strict-mutable.ts
const mutableProps = /* @__PURE__ */ new Map();
const mutableAssigned = /* @__PURE__ */ new Set();
const rule$5 = {
meta: {
docs: {
description: "This rule catches mutable Props that not need to be mutable.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "layout"
},
create(context) {
const stencil = stencilComponentContext();
function getMutable(node) {
if (!stencil.isComponent()) return;
const parsed = parseDecorator(node);
if (parsed && parsed.length && parsed[0].mutable || false) {
const varName = node.parent.key.name;
mutableProps.set(varName, node);
}
}
function checkAssigment(node) {
if (!stencil.isComponent()) return;
const propName = node.left.property.name;
mutableAssigned.add(propName);
}
stencil.rules["ClassDeclaration:exit"];
return {
"ClassDeclaration": stencil.rules.ClassDeclaration,
"PropertyDefinition > Decorator[expression.callee.name=Prop]": getMutable,
"AssignmentExpression[left.object.type=ThisExpression][left.property.type=Identifier]": checkAssigment,
"ClassDeclaration:exit": (node) => {
const isCmp = stencil.isComponent();
stencil.rules["ClassDeclaration:exit"](node);
if (isCmp) {
mutableAssigned.forEach((propName) => mutableProps.delete(propName));
mutableProps.forEach((varNode, varName) => {
context.report({
node: varNode.parent,
message: `@Prop() "${varName}" should not be mutable`
});
});
mutableAssigned.clear();
mutableProps.clear();
}
}
};
}
};
//#endregion
//#region src/rules/ban-side-effects.ts
const rule$4 = {
meta: {
docs: {
description: "This rule catches function calls at the top level",
category: "Possible Errors",
recommended: false
},
schema: [{
type: "array",
items: { type: "string" },
minLength: 0,
additionalProperties: false
}],
type: "suggestion"
},
create(context) {
const shouldSkip = /\b(spec|e2e|test)\./.test(context.filename);
const skipFunctions = context.options[0] || DEFAULTS;
if (shouldSkip) return {};
return { "CallExpression": (node) => {
if (skipFunctions.includes(node.callee.name)) return;
if (!isInScope(node)) context.report({
node,
message: `Call expressions at the top-level should be avoided.`
});
} };
}
};
const isInScope = (n) => {
const type = n.type;
if (type === "ArrowFunctionExpression" || type === "FunctionDeclaration" || type === "ClassDeclaration" || type === "ExportNamedDeclaration") return true;
n = n.parent;
if (n) return isInScope(n);
return false;
};
const DEFAULTS = [
"describe",
"test",
"bind",
"createStore"
];
//#endregion
//#region src/rules/strict-boolean-conditions.ts
/**
* @license
* Copyright 2016 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const OPTION_ALLOW_NULL_UNION = "allow-null-union";
const OPTION_ALLOW_UNDEFINED_UNION = "allow-undefined-union";
const OPTION_ALLOW_STRING = "allow-string";
const OPTION_ALLOW_ENUM = "allow-enum";
const OPTION_ALLOW_NUMBER = "allow-number";
const OPTION_ALLOW_MIX = "allow-mix";
const OPTION_ALLOW_BOOLEAN_OR_UNDEFINED = "allow-boolean-or-undefined";
const OPTION_ALLOW_ANY_RHS = "allow-any-rhs";
const rule$3 = {
meta: {
docs: {
description: `Restricts the types allowed in boolean expressions. By default only booleans are allowed.
The following nodes are checked:
* Arguments to the \`!\`, \`&&\`, and \`||\` operators
* The condition in a conditional expression (\`cond ? x : y\`)
* Conditions for \`if\`, \`for\`, \`while\`, and \`do-while\` statements.`,
category: "Possible Errors",
recommended: true
},
schema: [{
type: "array",
items: {
type: "string",
enum: [
OPTION_ALLOW_NULL_UNION,
OPTION_ALLOW_UNDEFINED_UNION,
OPTION_ALLOW_STRING,
OPTION_ALLOW_ENUM,
OPTION_ALLOW_NUMBER,
OPTION_ALLOW_BOOLEAN_OR_UNDEFINED,
OPTION_ALLOW_ANY_RHS
]
},
minLength: 0,
maxLength: 5
}],
type: "problem"
},
create(context) {
const parserServices = context.sourceCode.parserServices;
if (!parserServices?.esTreeNodeToTSNodeMap || !parserServices?.program) return {};
const program = parserServices.program;
const options = parseOptions(context.options[0] || [
"allow-null-union",
"allow-undefined-union",
"allow-boolean-or-undefined"
], true);
const checker = program.getTypeChecker();
function walk(sourceFile) {
typescript.forEachChild(sourceFile, function cb(node) {
switch (node.kind) {
case typescript.SyntaxKind.PrefixUnaryExpression: {
const { operator, operand } = node;
if (operator === typescript.SyntaxKind.ExclamationToken) checkExpression(operand, node);
break;
}
case typescript.SyntaxKind.IfStatement:
case typescript.SyntaxKind.WhileStatement:
case typescript.SyntaxKind.DoStatement: {
const c = node;
checkExpression(c.expression, c);
break;
}
case typescript.SyntaxKind.ConditionalExpression:
checkExpression(node.condition, node);
break;
case typescript.SyntaxKind.ForStatement: {
const { condition } = node;
if (condition !== void 0) checkExpression(condition, node);
}
}
return typescript.forEachChild(node, cb);
});
function checkExpression(node, location) {
const type = checker.getTypeAtLocation(node);
const failure = getTypeFailure(type, options);
if (failure !== void 0) {
if (failure === TypeFailure.AlwaysTruthy && !options.strictNullChecks && (options.allowNullUnion || options.allowUndefinedUnion)) return;
const originalNode = parserServices.tsNodeToESTreeNodeMap.get(node);
context.report({
node: originalNode,
message: showFailure(location, failure, isUnionType(type), options)
});
}
}
}
return { "Program": (node) => {
walk(parserServices.esTreeNodeToTSNodeMap.get(node));
} };
}
};
function parseOptions(ruleArguments, strictNullChecks) {
return {
strictNullChecks,
allowNullUnion: has(OPTION_ALLOW_NULL_UNION),
allowUndefinedUnion: has(OPTION_ALLOW_UNDEFINED_UNION),
allowString: has(OPTION_ALLOW_STRING),
allowEnum: has(OPTION_ALLOW_ENUM),
allowNumber: has(OPTION_ALLOW_NUMBER),
allowMix: has(OPTION_ALLOW_MIX),
allowBooleanOrUndefined: has(OPTION_ALLOW_BOOLEAN_OR_UNDEFINED),
allowAnyRhs: has(OPTION_ALLOW_ANY_RHS)
};
function has(name) {
return ruleArguments.indexOf(name) !== -1;
}
}
function getTypeFailure(type, options) {
if (isUnionType(type)) return handleUnion(type, options);
const kind = getKind(type);
const failure = failureForKind(kind, false, options);
if (failure !== void 0) return failure;
switch (triState(kind)) {
case true: return isTypeFlagSet(type, typescript.TypeFlags.Any | typescript.TypeFlags.BooleanLiteral) ? void 0 : TypeFailure.AlwaysTruthy;
case false: return isTypeFlagSet(type, typescript.TypeFlags.BooleanLiteral) ? void 0 : TypeFailure.AlwaysFalsy;
case void 0: return;
}
}
function isBooleanUndefined(type) {
let isTruthy = false;
for (const ty of type.types) if (isTypeFlagSet(ty, typescript.TypeFlags.Boolean)) isTruthy = true;
else if (isTypeFlagSet(ty, typescript.TypeFlags.BooleanLiteral)) isTruthy = isTruthy || ty.intrinsicName === "true";
else if (!isTypeFlagSet(ty, typescript.TypeFlags.Void | typescript.TypeFlags.Undefined)) return;
return isTruthy;
}
function handleUnion(type, options) {
if (options.allowBooleanOrUndefined) switch (isBooleanUndefined(type)) {
case true: return;
case false: return TypeFailure.AlwaysFalsy;
}
for (const ty of type.types) {
const failure = failureForKind(getKind(ty), true, options);
if (failure !== void 0) return failure;
}
}
/** Fails if a kind of falsiness is not allowed. */
function failureForKind(kind, isInUnion, options) {
switch (kind) {
case TypeKind.String:
case TypeKind.FalseStringLiteral: return options.allowString ? void 0 : TypeFailure.String;
case TypeKind.Number:
case TypeKind.FalseNumberLiteral: return options.allowNumber ? void 0 : TypeFailure.Number;
case TypeKind.Enum: return options.allowEnum ? void 0 : TypeFailure.Enum;
case TypeKind.Promise: return TypeFailure.Promise;
case TypeKind.Null: return isInUnion && !options.allowNullUnion ? TypeFailure.Null : void 0;
case TypeKind.Undefined: return isInUnion && !options.allowUndefinedUnion ? TypeFailure.Undefined : void 0;
default: return;
}
}
let TypeFailure = /* @__PURE__ */ function(TypeFailure) {
TypeFailure[TypeFailure["AlwaysTruthy"] = 0] = "AlwaysTruthy";
TypeFailure[TypeFailure["AlwaysFalsy"] = 1] = "AlwaysFalsy";
TypeFailure[TypeFailure["String"] = 2] = "String";
TypeFailure[TypeFailure["Number"] = 3] = "Number";
TypeFailure[TypeFailure["Null"] = 4] = "Null";
TypeFailure[TypeFailure["Undefined"] = 5] = "Undefined";
TypeFailure[TypeFailure["Enum"] = 6] = "Enum";
TypeFailure[TypeFailure["Mixes"] = 7] = "Mixes";
TypeFailure[TypeFailure["Promise"] = 8] = "Promise";
return TypeFailure;
}({});
var TypeKind = /* @__PURE__ */ function(TypeKind) {
TypeKind[TypeKind["String"] = 0] = "String";
TypeKind[TypeKind["FalseStringLiteral"] = 1] = "FalseStringLiteral";
TypeKind[TypeKind["Number"] = 2] = "Number";
TypeKind[TypeKind["FalseNumberLiteral"] = 3] = "FalseNumberLiteral";
TypeKind[TypeKind["Boolean"] = 4] = "Boolean";
TypeKind[TypeKind["FalseBooleanLiteral"] = 5] = "FalseBooleanLiteral";
TypeKind[TypeKind["Null"] = 6] = "Null";
TypeKind[TypeKind["Undefined"] = 7] = "Undefined";
TypeKind[TypeKind["Enum"] = 8] = "Enum";
TypeKind[TypeKind["AlwaysTruthy"] = 9] = "AlwaysTruthy";
TypeKind[TypeKind["Promise"] = 10] = "Promise";
return TypeKind;
}(TypeKind || {});
/** Divides a type into always true, always false, or unknown. */
function triState(kind) {
switch (kind) {
case TypeKind.String:
case TypeKind.Number:
case TypeKind.Boolean:
case TypeKind.Enum: return;
case TypeKind.Null:
case TypeKind.Undefined:
case TypeKind.FalseNumberLiteral:
case TypeKind.FalseStringLiteral:
case TypeKind.FalseBooleanLiteral: return false;
case TypeKind.AlwaysTruthy:
case TypeKind.Promise: return true;
}
}
function getKind(type) {
return is(typescript.TypeFlags.StringLike) ? TypeKind.String : is(typescript.TypeFlags.NumberLike) ? TypeKind.Number : is(typescript.TypeFlags.Boolean) ? TypeKind.Boolean : isObject("Promise") ? TypeKind.Promise : is(typescript.TypeFlags.Null) ? TypeKind.Null : is(typescript.TypeFlags.Undefined | typescript.TypeFlags.Void) ? TypeKind.Undefined : is(typescript.TypeFlags.EnumLike) ? TypeKind.Enum : is(typescript.TypeFlags.BooleanLiteral) ? type.intrinsicName === "true" ? TypeKind.AlwaysTruthy : TypeKind.FalseBooleanLiteral : TypeKind.AlwaysTruthy;
function is(flags) {
return isTypeFlagSet(type, flags);
}
function isObject(name) {
const symbol = type.getSymbol();
return symbol && symbol.getName() === name;
}
}
function binaryBooleanExpressionKind(node) {
switch (node.operatorToken.kind) {
case typescript.SyntaxKind.AmpersandAmpersandToken: return "&&";
case typescript.SyntaxKind.BarBarToken: return "||";
default: return;
}
}
function stringOr(parts) {
switch (parts.length) {
case 1: return parts[0];
case 2: return `${parts[0]} or ${parts[1]}`;
default:
let res = "";
for (let i = 0; i < parts.length - 1; i++) res += `${parts[i]}, `;
return `${res}or ${parts[parts.length - 1]}`;
}
}
function isUnionType(type) {
return isTypeFlagSet(type, typescript.TypeFlags.Union) && !isTypeFlagSet(type, typescript.TypeFlags.Enum);
}
function showLocation(n) {
switch (n.kind) {
case typescript.SyntaxKind.PrefixUnaryExpression: return "operand for the '!' operator";
case typescript.SyntaxKind.ConditionalExpression: return "condition";
case typescript.SyntaxKind.ForStatement: return "'for' condition";
case typescript.SyntaxKind.IfStatement: return "'if' condition";
case typescript.SyntaxKind.WhileStatement: return "'while' condition";
case typescript.SyntaxKind.DoStatement: return "'do-while' condition";
case typescript.SyntaxKind.BinaryExpression: return `operand for the '${binaryBooleanExpressionKind(n)}' operator`;
}
}
function showFailure(location, ty, unionType, options) {
const expectedTypes = showExpectedTypes(options);
const expected = expectedTypes.length === 1 ? `Only ${expectedTypes[0]}s are allowed` : `Allowed types are ${stringOr(expectedTypes)}`;
const tyFail = showTypeFailure(ty, unionType, options.strictNullChecks);
return `This type is not allowed in the ${showLocation(location)} because it ${tyFail}. ${expected}.`;
}
function showExpectedTypes(options) {
const parts = ["boolean"];
if (options.allowNullUnion) parts.push("null-union");
if (options.allowUndefinedUnion) parts.push("undefined-union");
if (options.allowString) parts.push("string");
if (options.allowEnum) parts.push("enum");
if (options.allowNumber) parts.push("number");
if (options.allowBooleanOrUndefined) parts.push("boolean-or-undefined");
return parts;
}
function showTypeFailure(ty, unionType, strictNullChecks) {
const is = unionType ? "could be" : "is";
switch (ty) {
case TypeFailure.AlwaysTruthy: return strictNullChecks ? "is always truthy" : `is always truthy. It may be null/undefined, but neither '${OPTION_ALLOW_NULL_UNION}' nor '${OPTION_ALLOW_UNDEFINED_UNION}' is set`;
case TypeFailure.AlwaysFalsy: return "is always falsy";
case TypeFailure.String: return `${is} a string`;
case TypeFailure.Number: return `${is} a number`;
case TypeFailure.Null: return `${is} null`;
case TypeFailure.Undefined: return `${is} undefined`;
case TypeFailure.Enum: return `${is} an enum`;
case TypeFailure.Promise: return "promise handled as boolean expression";
case TypeFailure.Mixes: return "unions more than one truthy/falsy type";
}
}
function isTypeFlagSet(obj, flag) {
return (obj.flags & flag) !== 0;
}
//#endregion
//#region src/rules/ban-exported-const-enums.ts
const rule$2 = {
meta: {
docs: {
description: "This rule catches exports of const enums",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "problem"
},
create(context) {
return { "ExportNamedDeclaration > TSEnumDeclaration[const]": (node) => {
context.report({
node,
message: `Exported const enums are not allowed`
});
} };
}
};
//#endregion
//#region src/rules/dependency-suggestions.ts
const rule$1 = {
meta: {
docs: {
description: "This rule can provide suggestions about dependencies in stencil apps",
recommended: true
},
schema: [],
type: "suggestion"
},
create(context) {
return { "ImportDeclaration": (node) => {
const message = SUGGESTIONS[node.source.value];
if (message) context.report({
node,
message
});
} };
}
};
const SUGGESTIONS = {
"classnames": `Stencil can already render conditional classes:
<div class={{disabled: condition}}>`,
"lodash": `"lodash" will bloat your build, use "lodash-es" instead: https://www.npmjs.com/package/lodash-es`,
"moment": `"moment" will bloat your build, use "dayjs", "date-fns" or other modern lightweight alternaitve`,
"core-js": `Stencil already include the core-js polyfills only when needed`
};
//#endregion
//#region src/rules/enforce-slot-jsdoc.ts
const rule = {
meta: {
docs: {
description: "Ensures slots are documented with JSDoc.",
category: "Possible Errors",
recommended: true
},
schema: [],
type: "problem"
},
create(context) {
const stencil = stencilComponentContext();
const implementedSlots = /* @__PURE__ */ new Set();
return {
...stencil.rules,
"ClassDeclaration:exit": (node) => {
if (stencil.isComponent()) {
const jsDocs = getJSDocComments(node, context.sourceCode);
const documentedSlots = new Set((jsDocs.length && jsDocs[0].tags.length ? jsDocs[0].tags.filter((tag) => tag.tagName === "slot") : []).map((tag) => {
const c = tag.comment.trim();
if (!c || c === "-" || c.startsWith("- ")) return "<default>";
const idx = c.indexOf(" - ");
return idx === -1 ? c : c.slice(0, idx).trim();
}));
const missingDocSlots = Array.from(implementedSlots).filter((slot) => !documentedSlots.has(slot));
const nonImplementedSlots = Array.from(documentedSlots).filter((slot) => !implementedSlots.has(slot));
missingDocSlots.forEach((slot) => {
context.report({
node,
message: slot === "<default>" ? "The default @slot must be documented." : `The @slot '${slot}' must be documented.`
});
});
nonImplementedSlots.forEach((slot) => {
context.report({
node,
message: slot === "<default>" ? "The default @slot is not implemented." : `The @slot '${slot}' is not implemented.`
});
});
}
implementedSlots.clear();
stencil.rules["ClassDeclaration:exit"](node);
},
JSXElement(node) {
if (node.openingElement.name.name !== "slot") return;
const nameAttribute = node.openingElement.attributes.find((attribute) => attribute.name.name === "name");
const slotName = nameAttribute && nameAttribute.value ? nameAttribute.value.value : "<default>";
implementedSlots.add(slotName);
}
};
}
};
//#endregion
//#region src/rules/index.ts
var rules_default = {
"ban-side-effects": rule$4,
"ban-default-true": rule$24,
"ban-exported-const-enums": rule$2,
"dependency-suggestions": rule$1,
"enforce-slot-jsdoc": rule,
"strict-boolean-conditions": rule$3,
"async-methods": rule$25,
"ban-prefix": rule$23,
"class-pattern": rule$22,
"decorators-context": rule$21,
"decorators-style": rule$20,
"element-type": rule$19,
"host-data-deprecated": rule$18,
"methods-must-be-public": rule$17,
"no-unused-watch": rule$16,
"own-methods-must-be-private": rule$15,
"own-props-must-be-private": rule$14,
"prefer-vdom-listener": rule$13,
"props-must-be-public": rule$12,
"props-must-be-readonly": rule$11,
"render-returns-host": rule$10,
"required-jsdoc":