Skip to content

Commit

Permalink
chore: ensure non-application CCs do not depend on application CCs
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone committed Sep 21, 2023
1 parent 6cd7316 commit db64876
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 213 deletions.
10 changes: 0 additions & 10 deletions packages/cc/maintenance/_tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,9 @@ import { generateCCAPIInterface } from "./generateCCAPIInterface";
import { generateCCExports } from "./generateCCExports";
import { generateCCValuesInterface } from "./generateCCValuesInterface";
// import { lintCCConstructors } from "./lintCCConstructor";
import { lintCCInterview } from "./lintCCInterview";

const argv = process.argv.slice(2);

const lint = () =>
Promise.all([
lintCCInterview(),
// lintCCConstructors(),
]);
const codegen = () =>
Promise.all([
generateCCAPIInterface(),
Expand All @@ -20,10 +14,6 @@ const codegen = () =>
]);

(async () => {
if (argv.includes("lint")) {
await lint();
}

if (argv.includes("codegen")) {
await codegen();
}
Expand Down
188 changes: 0 additions & 188 deletions packages/cc/maintenance/lintCCInterview.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/cc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
"build": "tsc -b tsconfig.build.json --pretty",
"clean": "del-cli build/ \"*.tsbuildinfo\"",
"extract-api": "yarn api-extractor run",
"lint:zwave": "yarn task lint",
"ts": "node -r esbuild-register",
"lint:ts": "eslint --cache --ext .ts \"src/**/*.ts\"",
"lint:ts:fix": "yarn run lint:ts --fix",
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
},
"devDependencies": {
"@typescript-eslint/utils": "^6.7.0",
"@zwave-js/core": "workspace:*",
"typescript": "5.2.2"
}
}
93 changes: 87 additions & 6 deletions packages/eslint-plugin/src/rules/consistent-cc-classes.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,47 @@
import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
import {
AST_NODE_TYPES,
ESLintUtils,
type TSESTree,
} from "@typescript-eslint/utils";
import { type CommandClasses, applicationCCs, getCCName } from "@zwave-js/core";
import path from "node:path";
import {
findDecorator,
findDecoratorContainingCCId,
getCCNameFromDecorator,
getCCIdFromDecorator,
getCCIdFromExpression,
} from "../utils";

function getRequiredInterviewCCsFromMethod(
method: TSESTree.MethodDefinition,
): { node: TSESTree.MemberExpression; ccId: CommandClasses }[] | undefined {
const returnExpression = method.value.body?.body.find(
(
s,
): s is TSESTree.ReturnStatement & {
argument: TSESTree.ArrayExpression;
} => s.type === AST_NODE_TYPES.ReturnStatement
&& s.argument?.type === AST_NODE_TYPES.ArrayExpression,
);
if (!returnExpression) return;

const memberExpressionsInArray = returnExpression.argument.elements.filter(
(e): e is TSESTree.MemberExpression =>
e?.type === AST_NODE_TYPES.MemberExpression,
);

// @ts-expect-error
return memberExpressionsInArray
.map((e) => ({
node: e,
ccId: getCCIdFromExpression(e),
}))
.filter(({ ccId }) => ccId != undefined);
}

export const consistentCCClasses = ESLintUtils.RuleCreator.withoutDocs({
create(context) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let currentCCName: string | undefined;
let currentCCId: CommandClasses | undefined;

return {
ClassDeclaration(node) {
Expand Down Expand Up @@ -47,7 +79,7 @@ export const consistentCCClasses = ESLintUtils.RuleCreator.withoutDocs({
messageId: "missing-cc-decorator",
});
} else {
currentCCName = getCCNameFromDecorator(ccDecorator);
currentCCId = getCCIdFromDecorator(ccDecorator);
}

// ...have a @implementedVersion decorator
Expand Down Expand Up @@ -99,8 +131,53 @@ export const consistentCCClasses = ESLintUtils.RuleCreator.withoutDocs({
});
}
},
MethodDefinition(node) {
// Only care about methods inside non-application CC classes,
// since only application CCs may depend on other application CCs
if (!currentCCId || applicationCCs.includes(currentCCId)) {
return;
}

// ...that are called determineRequiredCCInterviews
if (
node.key.type !== AST_NODE_TYPES.Identifier
|| node.key.name !== "determineRequiredCCInterviews"
) {
return;
}

const requiredCCs = getRequiredInterviewCCsFromMethod(node);
if (!requiredCCs) {
context.report({
node,
loc: node.loc,
messageId: "required-ccs-failed",
data: {
ccName: getCCName(currentCCId),
},
});
return;
}

const requiredApplicationCCs = requiredCCs
.filter((cc) => applicationCCs.includes(cc.ccId));
if (requiredApplicationCCs.length === 0) return;

// This is a non-application CC that depends on at least one application CC
for (const { node, ccId } of requiredApplicationCCs) {
context.report({
node,
loc: node.loc,
messageId: "must-not-depend-on-appl-cc",
data: {
ccName: getCCName(currentCCId),
applCCName: getCCName(ccId),
},
});
}
},
"ClassDeclaration:exit"(_node) {
currentCCName = undefined;
currentCCId = undefined;
},
};
},
Expand All @@ -122,6 +199,10 @@ export const consistentCCClasses = ESLintUtils.RuleCreator.withoutDocs({
"must-export": "Classes implementing a CC must be exported",
"must-inherit-commandclass":
"Classes implementing a CC MUST inherit from `CommandClass`",
"required-ccs-failed":
"Could not determine required CC interviews for `{{ccName}}`!",
"must-not-depend-on-appl-cc":
"Interview procedure of the non-application CC `{{ccName}}` must not depend on application CCs, but depends on `{{applCCName}}`!",
},
},
defaultOptions: [],
Expand Down
Loading

0 comments on commit db64876

Please sign in to comment.