Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: rule codes #83

Merged
merged 1 commit into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/__tests__/no-class-in-interface.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RuleTester } from "@typescript-eslint/rule-tester";

import { noClassInInterfaceProps } from "../no-class-in-interface-props.mjs";
import { noClassInInterface } from "../no-class-in-interface.mjs";

const ruleTester = new RuleTester({
languageOptions: {
Expand All @@ -12,7 +12,7 @@ const ruleTester = new RuleTester({
},
});

ruleTester.run("no-class-in-interface", noClassInInterfaceProps, {
ruleTester.run("no-class-in-interface", noClassInInterface, {
valid: [
// WHEN: property type is string
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { SymbolFlags } from "typescript";
* @returns An object containing the AST visitor functions
* @see {@link https://eslint-cdk-plugin.dev/rules/no-class-in-interface} - Documentation
*/
export const noClassInInterfaceProps = ESLintUtils.RuleCreator.withoutDocs({
export const noClassInInterface = ESLintUtils.RuleCreator.withoutDocs({
meta: {
type: "problem",
docs: {
Expand All @@ -22,7 +22,6 @@ export const noClassInInterfaceProps = ESLintUtils.RuleCreator.withoutDocs({
defaultOptions: [],
create(context) {
const parserServices = ESLintUtils.getParserServices(context);
const typeChecker = parserServices.program.getTypeChecker();
return {
TSInterfaceDeclaration(node) {
for (const property of node.body.body) {
Expand All @@ -34,8 +33,7 @@ export const noClassInInterfaceProps = ESLintUtils.RuleCreator.withoutDocs({
continue;
}

const tsNode = parserServices.esTreeNodeToTSNodeMap.get(property);
const type = typeChecker.getTypeAtLocation(tsNode);
const type = parserServices.getTypeAtLocation(property);
if (!type.symbol) continue;

// NOTE: check class type
Expand Down
23 changes: 7 additions & 16 deletions src/no-construct-stack-suffix.mts
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,13 @@ export const noConstructStackSuffix = ESLintUtils.RuleCreator.withoutDocs({
defaultOptions: [],
create(context) {
const parserServices = ESLintUtils.getParserServices(context);
const typeChecker = parserServices.program.getTypeChecker();
return {
NewExpression(node) {
const type = typeChecker.getTypeAtLocation(
parserServices.esTreeNodeToTSNodeMap.get(node)
);
if (!isConstructOrStackType(type)) {
const type = parserServices.getTypeAtLocation(node);
if (!isConstructOrStackType(type) || node.arguments.length < 2) {
return;
}

if (node.arguments.length < 2) return;

validateConstructId(node, context, node);
validateConstructId(node, context);
},
};
},
Expand All @@ -54,22 +48,19 @@ export const noConstructStackSuffix = ESLintUtils.RuleCreator.withoutDocs({
* Validate that construct ID does not end with "Construct" or "Stack"
*/
const validateConstructId = (
node: TSESTree.Node,
context: Context,
expression: TSESTree.NewExpression
node: TSESTree.NewExpression,
context: Context
): void => {
if (expression.arguments.length < 2) return;

// NOTE: Treat the second argument as ID
const secondArg = expression.arguments[1];
const secondArg = node.arguments[1];
if (
secondArg.type !== AST_NODE_TYPES.Literal ||
typeof secondArg.value !== "string"
) {
return;
}

const formattedConstructId = toPascalCase(secondArg.value as string);
const formattedConstructId = toPascalCase(secondArg.value);

if (formattedConstructId.endsWith("Construct")) {
context.report({
Expand Down
30 changes: 14 additions & 16 deletions src/no-import-private.mts
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,22 @@ export const noImportPrivate: Rule.RuleModule = {
const currentFilePath = context.filename;
const currentDirPath = path.dirname(currentFilePath);

if (importPath.includes("/private")) {
const absoluteCurrentDirPath = path.resolve(currentDirPath);
const absoluteImportPath = path.resolve(currentDirPath, importPath);
if (!importPath.includes("/private")) return;
const absoluteCurrentDirPath = path.resolve(currentDirPath);
const absoluteImportPath = path.resolve(currentDirPath, importPath);

// NOTE: Get the directory from the import path up to the private directory
const importDirBeforePrivate =
absoluteImportPath.split("/private")[0];
// NOTE: Get the directory from the import path up to the private directory
const importDirBeforePrivate = absoluteImportPath.split("/private")[0];

const currentDirSegments = getDirSegments(absoluteCurrentDirPath);
const importDirSegments = getDirSegments(importDirBeforePrivate);
if (
currentDirSegments.length !== importDirSegments.length ||
currentDirSegments.some(
(segment, index) => segment !== importDirSegments[index]
)
) {
context.report({ node, messageId: "noImportPrivate" });
}
const currentDirSegments = getDirSegments(absoluteCurrentDirPath);
const importDirSegments = getDirSegments(importDirBeforePrivate);
if (
currentDirSegments.length !== importDirSegments.length ||
currentDirSegments.some(
(segment, index) => segment !== importDirSegments[index]
)
) {
context.report({ node, messageId: "noImportPrivate" });
}
},
};
Expand Down
4 changes: 2 additions & 2 deletions src/no-mutable-props-interface.mts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ export const noMutablePropsInterface = ESLintUtils.RuleCreator.withoutDocs({
},
defaultOptions: [],
create(context) {
const sourceCode = context.sourceCode;

return {
TSInterfaceDeclaration(node) {
const sourceCode = context.sourceCode;

// NOTE: Interface name check for "Props"
if (!node.id.name.endsWith("Props")) return;

Expand Down
11 changes: 3 additions & 8 deletions src/no-mutable-public-fields.mts
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,12 @@ export const noMutablePublicFields = ESLintUtils.RuleCreator.withoutDocs({
defaultOptions: [],
create(context) {
const parserServices = ESLintUtils.getParserServices(context);
const typeChecker = parserServices.program.getTypeChecker();
const sourceCode = context.sourceCode;

return {
ClassDeclaration(node) {
const type = typeChecker.getTypeAtLocation(
parserServices.esTreeNodeToTSNodeMap.get(node)
);
if (!isConstructOrStackType(type)) {
return;
}
const sourceCode = context.sourceCode;
const type = parserServices.getTypeAtLocation(node);
if (!isConstructOrStackType(type)) return;

for (const member of node.body.body) {
// NOTE: check property definition
Expand Down
19 changes: 9 additions & 10 deletions src/no-parent-name-construct-id-match.mts
Original file line number Diff line number Diff line change
Expand Up @@ -299,15 +299,14 @@ const validateConstructId = ({

const formattedConstructId = toPascalCase(secondArg.value as string);
const formattedParentClassName = toPascalCase(parentClassName);
if (formattedParentClassName !== formattedConstructId) return;

if (formattedParentClassName === formattedConstructId) {
context.report({
node,
messageId: "noParentNameConstructIdMatch",
data: {
constructId: secondArg.value,
parentConstructName: parentClassName,
},
});
}
context.report({
node,
messageId: "noParentNameConstructIdMatch",
data: {
constructId: secondArg.value,
parentConstructName: parentClassName,
},
});
};
74 changes: 22 additions & 52 deletions src/no-public-class-fields.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
TSESLint,
TSESTree,
} from "@typescript-eslint/utils";
import { SymbolFlags, TypeChecker } from "typescript";
import { SymbolFlags } from "typescript";

import { isConstructOrStackType } from "./utils/typeCheck.mjs";

Expand All @@ -32,23 +32,13 @@ export const noPublicClassFields = ESLintUtils.RuleCreator.withoutDocs({
defaultOptions: [],
create(context) {
const parserServices = ESLintUtils.getParserServices(context);
const typeChecker = parserServices.program.getTypeChecker();
return {
ClassDeclaration(node) {
const type = typeChecker.getTypeAtLocation(
parserServices.esTreeNodeToTSNodeMap.get(node)
);
if (!isConstructOrStackType(type)) {
return;
}
const type = parserServices.getTypeAtLocation(node);
if (!isConstructOrStackType(type)) return;

// NOTE: Check class members
validateClassMember({
node,
context,
parserServices,
typeChecker,
});
validateClassMember(node, context, parserServices);

// NOTE: Check constructor parameter properties
const constructor = node.body.body.find(
Expand All @@ -62,12 +52,12 @@ export const noPublicClassFields = ESLintUtils.RuleCreator.withoutDocs({
) {
return;
}
validateConstructorParameterProperty({

validateConstructorParameterProperty(
constructor,
context,
parserServices,
typeChecker,
});
parserServices
);
},
};
},
Expand All @@ -77,17 +67,11 @@ export const noPublicClassFields = ESLintUtils.RuleCreator.withoutDocs({
* check the public variable of the class
* - if it is a class type, report an error
*/
const validateClassMember = ({
node,
context,
parserServices,
typeChecker,
}: {
node: TSESTree.ClassDeclaration;
context: Context;
parserServices: ParserServicesWithTypeInformation;
typeChecker: TypeChecker;
}) => {
const validateClassMember = (
node: TSESTree.ClassDeclaration,
context: Context,
parserServices: ParserServicesWithTypeInformation
) => {
for (const member of node.body.body) {
if (
member.type !== AST_NODE_TYPES.PropertyDefinition ||
Expand All @@ -102,13 +86,9 @@ const validateClassMember = ({
}

// NOTE: Skip fields without type annotation
if (!member.typeAnnotation) {
continue;
}

const tsNode = parserServices.esTreeNodeToTSNodeMap.get(member);
const type = typeChecker.getTypeAtLocation(tsNode);
if (!member.typeAnnotation) continue;

const type = parserServices.getTypeAtLocation(member);
if (!type.symbol) continue;

const isClass = type.symbol.flags === SymbolFlags.Class;
Expand All @@ -129,17 +109,11 @@ const validateClassMember = ({
* check the constructor parameter property
* - if it is a class type, report an error
*/
const validateConstructorParameterProperty = ({
constructor,
context,
parserServices,
typeChecker,
}: {
constructor: TSESTree.MethodDefinition;
context: Context;
parserServices: ParserServicesWithTypeInformation;
typeChecker: TypeChecker;
}) => {
const validateConstructorParameterProperty = (
constructor: TSESTree.MethodDefinition,
context: Context,
parserServices: ParserServicesWithTypeInformation
) => {
for (const param of constructor.value.params) {
if (
param.type !== AST_NODE_TYPES.TSParameterProperty ||
Expand All @@ -154,13 +128,9 @@ const validateConstructorParameterProperty = ({
}

// NOTE: Skip parameters without type annotation
if (!param.parameter.typeAnnotation) {
continue;
}

const tsNode = parserServices.esTreeNodeToTSNodeMap.get(param);
const type = typeChecker.getTypeAtLocation(tsNode);
if (!param.parameter.typeAnnotation) continue;

const type = parserServices.getTypeAtLocation(param);
if (!type.symbol) continue;

const isClass = type.symbol.flags === SymbolFlags.Class;
Expand Down
28 changes: 12 additions & 16 deletions src/no-variable-construct-id.mts
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,19 @@ export const noVariableConstructId = ESLintUtils.RuleCreator.withoutDocs({
defaultOptions: [],
create(context) {
const parserServices = ESLintUtils.getParserServices(context);
const typeChecker = parserServices.program.getTypeChecker();

return {
NewExpression(node) {
const type = typeChecker.getTypeAtLocation(
parserServices.esTreeNodeToTSNodeMap.get(node)
);
if (!isConstructType(type) || isStackType(type)) {
const type = parserServices.getTypeAtLocation(node);

if (
!isConstructType(type) ||
isStackType(type) ||
node.arguments.length < 2
) {
return;
}

if (node.arguments.length < 2) return;

validateConstructId(node, context, node);
validateConstructId(node, context);
},
};
},
Expand All @@ -51,16 +50,13 @@ export const noVariableConstructId = ESLintUtils.RuleCreator.withoutDocs({
* Check if the construct ID is a literal string
*/
const validateConstructId = (
node: TSESTree.Node,
context: Context,
expression: TSESTree.NewExpression
node: TSESTree.NewExpression,
context: Context
) => {
if (expression.arguments.length < 2 || isInsideLoop(node)) {
return;
}
if (node.arguments.length < 2 || isInsideLoop(node)) return;

// NOTE: Treat the second argument as ID
const secondArg = expression.arguments[1];
const secondArg = node.arguments[1];

// NOTE: When id is string literal, it's OK
if (
Expand Down
Loading
Loading