Skip to content

Commit

Permalink
chore: add lint to ensure CCs with constructors implement from()
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone committed Oct 22, 2024
1 parent decc987 commit 8b77138
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 3 deletions.
82 changes: 80 additions & 2 deletions packages/eslint-plugin/src/rules/consistent-cc-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ function getRequiredInterviewCCsFromMethod(
export const consistentCCClasses = ESLintUtils.RuleCreator.withoutDocs({
create(context) {
let currentCCId: CommandClasses | undefined;
let isInCCCommand = false;
let ctor: TSESTree.MethodDefinition | undefined;
let hasFromImpl: boolean;

return {
// Look at class declarations ending with "CC"
Expand Down Expand Up @@ -154,7 +157,23 @@ export const consistentCCClasses = ESLintUtils.RuleCreator.withoutDocs({
}
},
MethodDefinition(node) {
// Only care about methods inside non-application CC classes,
if (isInCCCommand) {
if (
node.key.type === AST_NODE_TYPES.Identifier
&& node.key.name === "from"
) {
hasFromImpl = true;
}

if (
node.key.type === AST_NODE_TYPES.Identifier
&& node.key.name === "constructor"
) {
ctor = node;
}
}

// For the following, 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;
Expand Down Expand Up @@ -198,8 +217,64 @@ export const consistentCCClasses = ESLintUtils.RuleCreator.withoutDocs({
});
}
},
"ClassDeclaration:exit"(_node) {

"ClassDeclaration:exit"(node) {
// Ensure each CC class with a custom constructor also has a from method
if (isInCCCommand && !!ctor && !hasFromImpl) {
const fix = (fixer: TSESLint.RuleFixer) => {
return fixer.insertTextAfter(
ctor!,
`
public static from(
raw: CCRaw,
ctx: CCParsingContext,
): ${node.id!.name} {
// TODO: Deserialize payload
throw new ZWaveError(
\`\${this.constructor.name}: deserialization not implemented\`,
ZWaveErrorCodes.Deserialization_NotImplemented,
);
}`,
);
};

context.report({
node: ctor,
loc: ctor.key.loc,
messageId: "missing-from-impl",
suggest: [
{
messageId: "suggest-impl-from",
fix,
},
],
});
}

currentCCId = undefined;
isInCCCommand = false;
hasFromImpl = false;
ctor = undefined;
},

// =================================================================

// Ensure consistent implementation of CC commands

// Look at class declarations containing, but not ending with "CC"
"ClassDeclaration[id.name=/.+CC.+/]"(
node: TSESTree.ClassDeclaration & {
id: TSESTree.Identifier;
},
) {
if (
node.superClass?.type === AST_NODE_TYPES.Identifier
&& node.superClass.name.endsWith("CC")
) {
// TODO: Implement more rules, for now only look at constructor/from
isInCCCommand = true;
}
},

// =================================================================
Expand Down Expand Up @@ -330,12 +405,15 @@ export const consistentCCClasses = ESLintUtils.RuleCreator.withoutDocs({
"Classes implementing a CC API must have a CC assigned using the `@API(...)` decorator",
"missing-version-decorator":
"Classes implementing a CC must be decorated with `@implementedVersion(...)`",
"missing-from-impl":
"CC implementations with a custom constructor must also override the `CommandClass.from(...)` method",
"must-export": "Classes implementing a CC must be exported",
"must-export-api": "Classes implementing a CC API must be exported",
"must-inherit-ccapi":
"Classes implementing a CC API MUST inherit from `CCAPI` or `PhysicalCCAPI`",
"suggest-extend-ccapi": "Inherit from `CCAPI`",
"suggest-extend-physicalccapi": "Inherit from `PhysicalCCAPI`",
"suggest-impl-from": "Override `CommandClass.from(...)`",
"must-inherit-commandclass":
"Classes implementing a CC MUST inherit from `CommandClass`",
"required-ccs-failed":
Expand Down
1 change: 0 additions & 1 deletion packages/eslint-plugin/src/rules/no-internal-cc-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export const noInternalCCTypes = ESLintUtils.RuleCreator.withoutDocs({
| TSESTree.TSInterfaceDeclaration
| TSESTree.TSTypeAliasDeclaration,
) {
if (node.id.name === "BasicCCSetOptions") debugger;
let fullNode:
| TSESTree.TSInterfaceDeclaration
| TSESTree.TSTypeAliasDeclaration
Expand Down

0 comments on commit 8b77138

Please sign in to comment.