UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

249 lines (228 loc) 7.92 kB
// /*! // * This scripts checks the constructors of Command Classes for common errors. // * Since v3.7.0, all report-type application CCs must call persistValues in their constructor, // * so values can be mapped from the root endpoint to endpoint 1. // */ // import { applicationCCs, CommandClasses } from "@zwave-js/core"; // import { // getCommandClassFromClassDeclaration, // loadTSConfig, // projectRoot, // reportProblem, // } from "@zwave-js/maintenance"; // import { blue, green } from "ansi-colors"; // import * as path from "path"; // import ts from "typescript"; // // Configure which CCs are excluded from this check // const whitelistedCCs: CommandClasses[] = [ // // Configuration CC has a different way of storing its values // CommandClasses.Configuration, // // Firmware Update CC is handled entirely on a request base // CommandClasses["Firmware Update Meta Data"], // ]; // function isCallToPersistValues(node: ts.Node): node is ts.CallExpression { // if ( // ts.isExpressionStatement(node) && // ts.isCallExpression(node.expression) && // ts.isPropertyAccessExpression(node.expression.expression) // ) { // const expr = node.expression.expression; // return ( // expr.expression.kind === ts.SyntaxKind.ThisKeyword && // expr.name.text === "persistValues" // ); // } // return false; // } // function isCheckForDeserializationOptions( // node: ts.Node, // ): node is ts.IfStatement & { thenStatement: ts.Block } { // return ( // ts.isIfStatement(node) && // ts.isCallExpression(node.expression) && // ts.isIdentifier(node.expression.expression) && // node.expression.expression.text === "gotDeserializationOptions" && // ts.isBlock(node.thenStatement) // ); // } // function getCommandClassFromClassOrParent( // checker: ts.TypeChecker, // sourceFile: ts.SourceFile, // node: ts.ClassDeclaration, // ): CommandClasses | undefined { // while (node) { // const ccId = getCommandClassFromClassDeclaration(sourceFile, node); // if (ccId != undefined) return ccId; // if (!node.heritageClauses) return; // const parentTypeClause = node.heritageClauses.find( // (c) => // c.token === ts.SyntaxKind.ExtendsKeyword && // c.types.length === 1, // ); // if (!parentTypeClause) return; // const symbol = checker.getSymbolAtLocation( // parentTypeClause.types[0].expression, // ); // if ( // !symbol || // !symbol.valueDeclaration || // !ts.isClassDeclaration(symbol.valueDeclaration) // ) { // return; // } // node = symbol.valueDeclaration; // } // } // export function lintCCConstructors(): Promise<void> { // // Create a Program to represent the project, then pull out the // // source file to parse its AST. // const tsConfig = loadTSConfig("zwave-js"); // const program = ts.createProgram(tsConfig.fileNames, tsConfig.options); // let hasError = false; // // Scan all source files // for (const sourceFile of program.getSourceFiles()) { // const relativePath = path // .relative(projectRoot, sourceFile.fileName) // .replace(/\\/g, "/"); // // Only look at files in this package // if (relativePath.startsWith("..")) continue; // // Only look at the commandclass dir // if (!relativePath.includes("/commandclass/")) { // continue; // } // // Ignore test files, compiled files and the index // if ( // relativePath.endsWith(".test.ts") || // relativePath.endsWith("index.ts") || // // and the manufacturer proprietary implementations // relativePath.includes("/manufacturerProprietary/") // ) { // continue; // } // // Visit each CC class and see if it overwrites determineRequiredCCInterviews // ts.forEachChild(sourceFile, (node) => { // // Only look at CC class declarations ending with "Report" // if ( // ts.isClassDeclaration(node) && // node.name && // node.name.text.includes("CC") && // node.name.text.endsWith("Report") // ) { // const classLocation = ts.getLineAndCharacterOfPosition( // sourceFile, // node.getStart(sourceFile, false), // ); // const fail = ( // reason: string, // severity: "error" | "warn" = "error", // ) => { // if (severity === "error") hasError = true; // reportProblem({ // severity, // filename: relativePath, // line: classLocation.line + 1, // message: reason, // }); // }; // // Only look at the constructor // const constructor = node.members.find( // (member): member is ts.ConstructorDeclaration => // ts.isConstructorDeclaration(member), // ); // if (!constructor || !constructor.body) { // if (node.name.text.endsWith("Report")) { // fail( // `The CC report class ${node.name.text} has no constructor!`, // ); // } // return; // } // const ccId = getCommandClassFromClassOrParent( // program.getTypeChecker(), // sourceFile, // node, // ); // // Ignore whitelisted CCs // if (ccId != undefined && whitelistedCCs.includes(ccId)) return; // // Error only for Application CCs // const isApplicationCC = // ccId != undefined && applicationCCs.includes(ccId); // const severity = isApplicationCC ? "error" : "warn"; // // persistValues must be called: // // a) in CCs with name *Report (mandatory): // // a1) either in the constructor body // // a2) or inside an if statement with argument gotDeserializationOptions // // b) except if: // // b1) there is a method definition for mergePartialCCs // // b2) there is a @noCCValues comment // const hasCallToPersistValuesInConstructorBody = // !!constructor.body.statements.find(isCallToPersistValues); // const checkForDeserializationOptions = // constructor.body.statements.find( // isCheckForDeserializationOptions, // ); // const hasCallToPersistValuesInCheck = // checkForDeserializationOptions && // !!checkForDeserializationOptions.thenStatement.statements.find( // isCallToPersistValues, // ); // const hasMergePartialCCsImplementation = !!node.members.find( // (member) => // ts.isMethodDeclaration(member) && // ts.isIdentifier(member.name) && // member.name.text === "mergePartialCCs", // ); // const hasNoCCValuesComment = node.members.some((member) => { // const sourceText = sourceFile.getText(); // const comments = ts.getLeadingCommentRanges( // sourceText, // member.getFullStart(), // ); // if (!comments) return false; // const commentTexts = comments.map((c) => // sourceText.slice(c.pos, c.end), // ); // return commentTexts.some((c) => // c.trim().startsWith("// @noCCValues"), // ); // }); // // b) // if (hasMergePartialCCsImplementation || hasNoCCValuesComment) { // return; // } // // a) // if (hasCallToPersistValuesInConstructorBody) return; // if (hasCallToPersistValuesInCheck) return; // fail( // `The${ // isApplicationCC ? " application" : "" // } CC report class ${blue(node.name.text)} ${ // severity === "error" ? "needs" : "might need" // } a call to ${blue( // "persistValues()", // )} in the constructor or an implementation for ${blue( // "mergePartialCCs()", // )} // If this is a false-positive, consider suppressing this error with a ${green( // "// @noCCValues", // )} comment.`, // severity, // ); // return; // } // }); // } // if (hasError) { // return Promise.reject( // new Error( // "Linting the CC constructors was not successful! See log output for details.", // ), // ); // } else { // return Promise.resolve(); // } // } // if (require.main === module) // lintCCConstructors() // .then(() => process.exit(0)) // .catch(() => process.exit(1));