eslint-plugin-ngxs-style-guide
Version:
ESLint rules for ngxs state manager
397 lines (385 loc) • 11.1 kB
JavaScript
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 __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
var __export = (target, all) => {
__markAsModule(target);
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __reExport = (target, module2, desc) => {
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
for (let key of __getOwnPropNames(module2))
if (!__hasOwnProp.call(target, key) && key !== "default")
__defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable });
}
return target;
};
var __toModule = (module2) => {
return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2);
};
// src/index.ts
__export(exports, {
configs: () => configs,
rules: () => rules
});
// src/action-suffixes/action-suffixes.ts
function actionSuffixes(context) {
let className;
let isActionClass = false;
return {
ClassDeclaration(node) {
className = node.id && node.id.name;
},
"ClassDeclaration:exit"(node) {
if (isActionClass && className && className.endsWith("Action") && node.id) {
context.report({
messageId: "default",
node: node.id
});
}
isActionClass = false;
className = void 0;
},
PropertyDefinition(node) {
if (className && node.value && node.value.type === "Literal") {
const value = String(node.value.value);
if (/^\[[A-Z][A-Za-z]+/.test(value)) {
isActionClass = true;
}
}
}
};
}
var rule = {
create: actionSuffixes,
meta: {
type: "suggestion",
schema: {},
docs: {
description: "Actions should NOT have a suffix",
recommended: "warn",
url: "https://www.ngxs.io/recipes/style-guide#action-suffixes"
},
messages: {
default: "Actions should NOT have a suffix `Action`"
}
}
};
// src/no-pipe-dispatch/no-pipe-dispatch.ts
function noPipeDispatch(context) {
return {
MemberExpression(node) {
if (node.object.type === "CallExpression" && node.object.callee.type === "MemberExpression" && node.object.callee.property.type === "Identifier" && node.object.callee.property.name === "dispatch" && node.property.type === "Identifier" && node.property.name === "pipe") {
context.report({
messageId: "default",
node: node.property
});
}
}
};
}
var rule2 = {
create: noPipeDispatch,
meta: {
type: "problem",
schema: {},
docs: {
description: "No pipe() after dispatch",
recommended: "warn",
url: ""
},
messages: { default: "No pipe() after dispatch" }
}
};
// src/utils.ts
var import_experimental_utils = __toModule(require("@typescript-eslint/experimental-utils"));
function getDecoratorByName(node, name) {
return node.decorators?.find((d) => {
const expression = d.expression.type === "CallExpression" && d.expression;
return expression && expression.callee.type === "Identifier" && expression.callee.name === name;
});
}
function isClassDeclaration(node) {
return node.type === "ClassDeclaration";
}
function hasStateDecorator(node) {
const decorator = getDecoratorByName(node, "State");
return decorator != void 0;
}
function hasActionDecorator(node) {
const decorator = getDecoratorByName(node, "Action");
return decorator != void 0;
}
function hasSelectDecorator(node) {
const decorator = getDecoratorByName(node, "Select");
return decorator != void 0;
}
function isImplements(node, interfaceName) {
return Boolean(node.implements?.some((node2) => {
return node2.expression.type === "Identifier" && node2.expression.name === interfaceName;
}));
}
function isIdentifierEndsWith(node, name) {
if (!node.id) {
return true;
}
return node.id.name.endsWith(name);
}
function getParent(node, predicate) {
let result;
while (node && node.parent) {
node = node.parent;
if (predicate(node)) {
result = node;
break;
}
}
return result;
}
function getParentFunction(node) {
return getParent(node, (n) => n.type === "MethodDefinition");
}
function getParentClass(node) {
return getParent(node, (n) => n.type === "ClassDeclaration");
}
// src/no-subscribe-in-actions/no-subscribe-in-actions.ts
function noSubscribeInActions(context) {
return {
MemberExpression(node) {
if (node.property.type === "Identifier" && node.property.name === "subscribe") {
const method = getParentFunction(node);
if (method && hasActionDecorator(method)) {
const pclass = getParentClass(method);
if (pclass && hasStateDecorator(pclass)) {
context.report({
messageId: "default",
node: node.property
});
}
}
}
}
};
}
var rule3 = {
create: noSubscribeInActions,
meta: {
type: "suggestion",
schema: {},
docs: {
description: "",
recommended: "warn",
url: "https://stackoverflow.com/questions/53047853"
},
messages: { default: "Do not subscribe in actions, return Observable" }
}
};
// src/plugin-suffix/plugin-suffix.ts
function pluginSuffix(context) {
return {
ClassDeclaration(node) {
if (node.id && isImplements(node, "NgxsPlugin") && !isIdentifierEndsWith(node, "Plugin")) {
context.report({
messageId: "default",
node: node.id
});
}
}
};
}
var rule4 = {
create: pluginSuffix,
meta: {
type: "suggestion",
schema: {},
docs: {
description: "Plugins should end with the `Plugin` suffix",
recommended: "warn",
url: "https://www.ngxs.io/recipes/style-guide#plugin-suffix"
},
messages: {
default: "Plugins should end with the `Plugin` suffix"
}
}
};
// src/select-suffix/select-suffix.ts
function selectSuffix(context) {
return {
PropertyDefinition(node) {
if (!hasSelectDecorator(node)) {
return;
}
const propertyName = node.key.name;
if (propertyName && !propertyName.endsWith("$")) {
context.report({
messageId: "default",
node: node.key,
fix: (fixer) => {
const newName = `${propertyName}$`;
return fixer.replaceTextRange(node.key.range, newName);
}
});
}
}
};
}
var rule5 = {
create: selectSuffix,
meta: {
docs: {
description: "Selects should have a `$` suffix",
url: "https://www.ngxs.io/recipes/style-guide#state-suffix",
recommended: "warn"
},
type: "suggestion",
fixable: "code",
messages: {
default: "Selects should have a `$` suffix"
},
schema: {}
}
};
// src/state-filenames/state-filenames.ts
function stateFilenames(context) {
const filenameWithExtension = context.getFilename();
if (filenameWithExtension === "<input>" || filenameWithExtension === "<text>" || !filenameWithExtension) {
return {};
}
let hasDecorator = false;
return {
ClassDeclaration(node) {
hasDecorator = hasStateDecorator(node);
},
Program() {
hasDecorator = false;
},
"Program:exit"(node) {
if (hasDecorator && !filenameWithExtension.endsWith(".state.ts")) {
context.report({
messageId: "default",
node
});
}
}
};
}
var rule6 = {
create: stateFilenames,
meta: {
docs: {
description: "States should have a `.state.ts` suffix for the filename",
url: "https://www.ngxs.io/recipes/style-guide#state-filenames",
recommended: "warn"
},
type: "suggestion",
messages: {
default: "States should have a `.state.ts` suffix for the filename"
},
schema: {}
}
};
// src/state-interfaces/state-interfaces.ts
function stateInterfaces(context) {
return {
ClassDeclaration(node) {
if (!isClassDeclaration(node)) {
throw new TypeError(`Unexpected node type (${node.type}), expected ClassDeclaration`);
}
const decoratorNode = getDecoratorByName(node, "State");
const typeName = decoratorNode?.expression?.typeParameters?.params?.[0]?.typeName;
if (typeName && !typeName.name.endsWith("Model")) {
context.report({
messageId: "default",
node,
fix: (fixer) => {
let result = null;
if (node.id) {
const newName = `${typeName.name}Model`;
result = fixer.replaceTextRange(typeName.range, newName);
}
return result;
}
});
}
}
};
}
var rule7 = {
create: stateInterfaces,
meta: {
type: "suggestion",
fixable: "code",
schema: {},
docs: {
description: "State interfaces should be named the name of the state followed by the `Model` suffix",
recommended: "warn",
url: "https://www.ngxs.io/recipes/style-guide#state-interfaces"
},
messages: {
default: "State interfaces should be named the name of the state followed by the `Model` suffix"
}
}
};
// src/state-suffix/state-suffix.ts
function stateSuffix(context) {
return {
ClassDeclaration(node) {
if (hasStateDecorator(node) && node.id && !isIdentifierEndsWith(node, "State")) {
context.report({
messageId: "default",
node: node.id,
fix: (fixer) => {
let result = null;
if (node.id) {
const newName = `${node.id.name}State`;
result = fixer.replaceTextRange(node.id.range, newName);
}
return result;
}
});
}
}
};
}
var rule8 = {
create: stateSuffix,
meta: {
type: "suggestion",
fixable: "code",
schema: {},
docs: {
description: "A state should always be suffixed with the word `State`",
recommended: "warn",
url: "https://www.ngxs.io/recipes/style-guide#state-suffix"
},
messages: {
default: "A state should always be suffixed with the word `State`"
}
}
};
// src/index.ts
var rules = {
"state-filenames": rule6,
"state-interfaces": rule7,
"action-suffixes": rule,
"state-suffix": rule8,
"plugin-suffix": rule4,
"select-suffix": rule5,
"no-subscribe-in-actions": rule3,
"no-pipe-dispatch": rule2
};
var configs = {
recommended: {
rules: Object.fromEntries(Object.keys(rules).filter((rule9) => rule9 !== "no-pipe-dispatch").map((rule9) => [`ngxs-style-guide/${rule9}`, "warn"]))
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
configs,
rules
});
//# sourceMappingURL=index.js.map