eslint-plugin-react-edge
Version:
ESLint plugin with niche rules for React projects
385 lines (371 loc) • 13.4 kB
JavaScript
//#region rolldown:runtime
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
const __typescript_eslint_utils = __toESM(require("@typescript-eslint/utils"));
//#region package.json
var name = "eslint-plugin-react-edge";
var version = "0.1.2";
//#endregion
//#region src/utils/index.ts
const docBaseUrl = "https://github.com/kokororin/eslint-plugin-react-edge/blob/master/src/rules/";
const createRule = __typescript_eslint_utils.ESLintUtils.RuleCreator((name$1) => `${docBaseUrl}${name$1}.md`);
/**
* check if a value is a valid camel case string
* @param value - value to check
*/
function isCamelCase(value) {
return /^[a-z][a-zA-Z0-9]*$/.test(value);
}
/**
* check if a value is a valid pascal case string
* @param value - value to check
*/
function isPascalCase(value) {
return /^[A-Z][A-Za-z0-9]*$/.test(value);
}
/**
* check if a value is a valid upper case string
* @param value - value to check
*/
function isUpperCase(value) {
return /^[A-Z0-9_]+$/.test(value);
}
//#endregion
//#region src/rules/prefer-named-property-access.ts
const RULE_NAME$1 = "prefer-named-property-access";
/**
* Ensures that passed key is imported from 'react' package.
* @param context - The rule context
* @param fixer - The rule fixer
* @param key - The key to import
* @yields The fix to apply
*/
function* updateImportStatement(context, fixer, key) {
const sourceCode = context.sourceCode;
const importNode = sourceCode.ast.body.find((node) => node.type === __typescript_eslint_utils.AST_NODE_TYPES.ImportDeclaration && node.source.value === "react");
if (!importNode) {
yield fixer.insertTextBefore(sourceCode.ast.body[0], `import { ${key} } from 'react';\n`);
return;
}
if (importNode.specifiers.length === 1 && importNode.specifiers[0].type === __typescript_eslint_utils.AST_NODE_TYPES.ImportDefaultSpecifier) {
yield fixer.insertTextAfter(importNode.specifiers[0], `, { ${key} }`);
return;
}
const alreadyImportedKeys = importNode.specifiers.filter((specifier) => specifier.type === __typescript_eslint_utils.AST_NODE_TYPES.ImportSpecifier).map((specifier) => {
if (specifier.imported.type === __typescript_eslint_utils.AST_NODE_TYPES.Identifier) return specifier.imported.name;
return void 0;
}).filter((name$1) => name$1 !== void 0);
if (alreadyImportedKeys.includes(key)) return;
const lastSpecifier = importNode.specifiers[importNode.specifiers.length - 1];
if (lastSpecifier == null) {
yield fixer.insertTextBefore(sourceCode.ast.body[0], `import { ${key} } from 'react';\n`);
return;
}
yield fixer.insertTextAfter(lastSpecifier, `, ${key}`);
}
var prefer_named_property_access_default = createRule({
name: RULE_NAME$1,
meta: {
type: "problem",
fixable: "code",
docs: { description: "Enforce importing each member of React namespace separately instead of accessing them through React namespace" },
messages: {
illegalReactPropertyAccess: "Illegal React property access: {{name}}. Use named import instead.",
disallowImportReactEvent: "Disallow importing React event types to avoid conflicts with global event types."
},
schema: []
},
defaultOptions: [],
create(context) {
return {
TSQualifiedName(node) {
if (!("name" in node.left) || node.left.name !== "React" || !("name" in node.right) || node.right.name.endsWith("Event")) return;
context.report({
node,
messageId: "illegalReactPropertyAccess",
data: { name: node.right.name },
*fix(fixer) {
yield fixer.replaceText(node, node.right.name);
yield* updateImportStatement(context, fixer, node.right.name);
}
});
},
MemberExpression(node) {
if (node.object.type !== __typescript_eslint_utils.AST_NODE_TYPES.Identifier || node.object.name !== "React" || node.property.type !== __typescript_eslint_utils.AST_NODE_TYPES.Identifier) return;
const propertyName = node.property.name;
context.report({
node,
messageId: "illegalReactPropertyAccess",
data: { name: propertyName },
*fix(fixer) {
yield fixer.replaceText(node, propertyName);
yield* updateImportStatement(context, fixer, propertyName);
}
});
},
ImportDeclaration(node) {
if (node.source.value !== "react" && node.source.value !== "preact") return;
node.specifiers.forEach((specifier) => {
if (specifier.type === __typescript_eslint_utils.AST_NODE_TYPES.ImportSpecifier && specifier.imported.type === __typescript_eslint_utils.AST_NODE_TYPES.Identifier && specifier.imported.name.endsWith("Event")) context.report({
node: specifier,
messageId: "disallowImportReactEvent"
});
});
}
};
}
});
//#endregion
//#region src/rules/var-naming.ts
const RULE_NAME = "var-naming";
const reactGlobalFuncs = new Set([
"createContext",
"forwardRef",
"lazy",
"memo"
]);
const reactFCTypes = new Set([
"FC",
"FunctionComponent",
"VFC",
"VoidFunctionComponent"
]);
const defaultExcludeTypes = ["StoryObj", "StoryFn"];
const defaultExcludeNames = ["^(__dirname|__filename)$", "(.*)Event$"];
const defaultOptions = [{
funcFormat: ["camelCase"],
varFormat: ["camelCase", "UPPER_CASE"],
excludeNames: [],
excludeFuncs: [],
excludeTypes: []
}];
var var_naming_default = createRule({
name: RULE_NAME,
meta: {
type: "problem",
docs: { description: "Enforce variable and function naming convention" },
schema: [{
type: "object",
properties: {
funcFormat: {
type: "array",
items: {
type: "string",
enum: [
"camelCase",
"PascalCase",
"UPPER_CASE"
]
}
},
varFormat: {
type: "array",
items: {
type: "string",
enum: [
"camelCase",
"PascalCase",
"UPPER_CASE"
]
}
},
excludeNames: {
type: "array",
items: { type: "string" }
},
excludeFuncs: {
type: "array",
items: { type: "string" }
},
excludeTypes: {
type: "array",
items: { type: "string" }
}
}
}],
messages: {
invalidFuncNaming: "Invalid function naming for non-React component, expected {{formats}}",
invalidReactFCNaming: "Invalid naming convention for React functional component, expected PascalCase",
invalidVarNaming: "Invalid variable naming, expected {{formats}}"
}
},
defaultOptions,
create(context) {
const options = {
...defaultOptions[0],
...context.options[0]
};
const funcFormat = options.funcFormat;
const varFormat = options.varFormat;
const excludeNames = [...defaultExcludeNames, ...options.excludeNames ?? []];
const excludeFuncs = options.excludeFuncs;
const excludeTypes = [...defaultExcludeTypes, ...options.excludeTypes ?? []];
function validate(type, { node, name: name$1 }) {
let isPass = false;
let formats;
let messageId;
switch (type) {
case "func":
formats = funcFormat;
messageId = "invalidFuncNaming";
break;
case "var":
formats = varFormat;
messageId = "invalidVarNaming";
break;
}
for (const format of formats) switch (format) {
case "camelCase":
if (isCamelCase(name$1)) isPass = true;
break;
case "PascalCase":
if (isPascalCase(name$1)) isPass = true;
break;
case "UPPER_CASE":
if (isUpperCase(name$1)) isPass = true;
break;
}
if (!isPass) context.report({
node,
messageId,
data: { formats: formats.join(", ") }
});
}
function checkJSXElement(node) {
if (!node) return false;
if (node.type === __typescript_eslint_utils.AST_NODE_TYPES.JSXElement || node.type === __typescript_eslint_utils.AST_NODE_TYPES.JSXFragment) return true;
if (node.type === __typescript_eslint_utils.AST_NODE_TYPES.BlockStatement) {
for (const statement of node.body) if (statement.type === __typescript_eslint_utils.AST_NODE_TYPES.ReturnStatement) {
if (checkJSXElement(statement.argument)) return true;
} else if (checkJSXElement(statement)) return true;
}
if (node.type === __typescript_eslint_utils.AST_NODE_TYPES.ArrowFunctionExpression || node.type === __typescript_eslint_utils.AST_NODE_TYPES.FunctionExpression) return checkJSXElement(node.body);
return false;
}
function getTypeReference(node) {
if (node.id.typeAnnotation?.typeAnnotation && node.id.typeAnnotation.typeAnnotation.type === __typescript_eslint_utils.AST_NODE_TYPES.TSTypeReference && node.id.typeAnnotation.typeAnnotation.typeName.type === __typescript_eslint_utils.AST_NODE_TYPES.Identifier) {
const typeName = node.id.typeAnnotation.typeAnnotation.typeName.name;
return typeName.split(".").pop();
}
return void 0;
}
return {
FunctionDeclaration(node) {
if (node.id) {
const fnName = node.id.name;
const isReactComponent = checkJSXElement(node.body);
if (!isReactComponent) validate("func", {
node,
name: fnName
});
}
},
VariableDeclarator(node) {
if (node.id != null && node.init && (node.init.type === __typescript_eslint_utils.AST_NODE_TYPES.FunctionExpression || node.init.type === __typescript_eslint_utils.AST_NODE_TYPES.ArrowFunctionExpression)) {
const fnName = "name" in node.id ? node.id.name : "";
if (!fnName) return;
let isReactComponent = checkJSXElement(node.init.body);
const typeName = getTypeReference(node);
if (typeName != null && reactFCTypes.has(typeName)) isReactComponent = true;
if (!isReactComponent) validate("func", {
node,
name: fnName
});
} else if (node.id != null && node.init && node.init.type === __typescript_eslint_utils.AST_NODE_TYPES.LogicalExpression) {
const varName = "name" in node.id ? node.id.name : "";
if (!varName) return;
const parts = [node.init.left, node.init.right];
let partIsReactComponent = false;
for (const part of parts) if (part.type === __typescript_eslint_utils.AST_NODE_TYPES.FunctionExpression || part.type === __typescript_eslint_utils.AST_NODE_TYPES.ArrowFunctionExpression) {
const isReactComponent = checkJSXElement(part.body);
if (isReactComponent) partIsReactComponent = true;
}
if (!partIsReactComponent) validate("var", {
node,
name: varName
});
} else if (node.id != null && "name" in node.id) {
const varName = node.id.name;
for (const excludeRegex of excludeNames) if (new RegExp(excludeRegex).test(varName)) return;
const typeName = getTypeReference(node);
if (typeName != null) {
for (const excludeRegex of excludeTypes) if (new RegExp(excludeRegex).test(typeName)) return;
}
if (node.init) {
let calleeName;
let shouldCheckReact = false;
let initNode;
if (node.init.type === __typescript_eslint_utils.AST_NODE_TYPES.CallExpression) initNode = node.init;
else if (node.init.type === __typescript_eslint_utils.AST_NODE_TYPES.TSAsExpression && node.init.expression != null && node.init.expression.type === __typescript_eslint_utils.AST_NODE_TYPES.CallExpression) initNode = node.init.expression;
if (initNode) {
shouldCheckReact = true;
if (initNode.callee.type === __typescript_eslint_utils.AST_NODE_TYPES.Identifier) calleeName = initNode.callee.name;
else if (initNode.callee.type === __typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && initNode.callee.property.type === __typescript_eslint_utils.AST_NODE_TYPES.Identifier) calleeName = initNode.callee.property.name;
}
if (calleeName != null) {
for (const excludeRegex of excludeFuncs) if (new RegExp(excludeRegex).test(calleeName)) return;
}
if (shouldCheckReact) {
if (calleeName == null) return;
if (reactGlobalFuncs.has(calleeName) || reactGlobalFuncs.has(calleeName.split(".").pop() ?? "")) return;
}
}
validate("var", {
node,
name: varName
});
}
}
};
}
});
//#endregion
//#region src/rules/index.ts
const rules = {
[RULE_NAME$1]: prefer_named_property_access_default,
[RULE_NAME]: var_naming_default
};
//#endregion
//#region src/index.ts
const reactEdge = {
meta: {
name,
version
},
rules
};
const allRules = Object.fromEntries(Object.keys(rules).map((name$1) => [`react-edge/${name$1}`, "error"]));
const configs = { recommended: createConfig(allRules, "react-edge/recommended") };
function createConfig(rules$1, configName) {
return {
name: configName,
plugins: { "react-edge": reactEdge },
rules: rules$1
};
}
const allConfigs = {
...reactEdge,
configs
};
var src_default = allConfigs;
//#endregion
module.exports = src_default;