Skip to content

Commit

Permalink
chore(lint): check if unsigned flag is necessary and add or remove it
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone committed Sep 26, 2023
1 parent 6e2a9a1 commit 6b66c18
Show file tree
Hide file tree
Showing 5 changed files with 460 additions and 47 deletions.
3 changes: 2 additions & 1 deletion packages/config/config/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
],
rules: {
"@zwave-js/consistent-device-config-property-order": "error",
"@zwave-js/no-unnecessary-min-max-value": "error"
"@zwave-js/no-unnecessary-min-max-value": "error",
"@zwave-js/auto-unsigned": "error",
}
};
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { autoUnsigned } from "./rules/auto-unsigned.js";
import { ccAPIValidateArgs } from "./rules/ccapi-validate-args.js";
import { consistentCCClasses } from "./rules/consistent-cc-classes.js";
import { consistentDeviceConfigPropertyOrder } from "./rules/consistent-device-config-property-order.js";
Expand All @@ -14,5 +15,6 @@ module.exports = {
"consistent-device-config-property-order":
consistentDeviceConfigPropertyOrder,
"no-unnecessary-min-max-value": noUnnecessaryMinMaxValue,
"auto-unsigned": autoUnsigned,
},
};
191 changes: 191 additions & 0 deletions packages/eslint-plugin/src/rules/auto-unsigned.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { getIntegerLimits } from "@zwave-js/core";
import type { AST } from "jsonc-eslint-parser";
import {
type JSONCRule,
getJSONBoolean,
getJSONNumber,
insertAfterJSONProperty,
insertBeforeJSONProperty,
paramInfoPropertyOrder,
removeJSONProperty,
} from "../utils";

export const autoUnsigned: JSONCRule.RuleModule = {
create(context) {
if (!context.parserServices.isJSON) {
return {};
}
return {
// Avoid unnecessary min/max value in parameters with predefined options
"JSONProperty[key.value='paramInformation'] > JSONArrayExpression > JSONObjectExpression"(
node: AST.JSONObjectExpression,
) {
// We cannot handle imports yet
const hasImport = node.properties.some((p) =>
p.key.type === "JSONLiteral"
&& p.key.value === "$import"
);
if (hasImport) return;

const valueSizeProperty = getJSONNumber(node, "valueSize");
if (!valueSizeProperty) return;
const { value: valueSize } = valueSizeProperty;

const minValueProperty = getJSONNumber(node, "minValue");
if (!minValueProperty) return;
const { value: minValue } = minValueProperty;

const maxValueProperty = getJSONNumber(node, "maxValue");
if (!maxValueProperty) return;
const { value: maxValue } = maxValueProperty;

const unsignedProperty = getJSONBoolean(node, "unsigned");
const isUnsigned = !!unsignedProperty?.value;

// Determine if the min/max value match the value size
const limits = getIntegerLimits(valueSize as any, true);
const unsignedLimits = getIntegerLimits(
valueSize as any,
false,
);
if (!limits) {
context.report({
loc: valueSizeProperty.node.loc,
messageId: "invalid-value-size",
data: { valueSize: valueSize.toString() },
});
return;
}

const fitsSignedLimits = minValue >= limits.min
&& minValue <= limits.max
&& maxValue >= limits.min
&& maxValue <= limits.max;
const fitsUnsignedLimits = minValue >= unsignedLimits.min
&& minValue <= unsignedLimits.max
&& maxValue >= unsignedLimits.min
&& maxValue <= unsignedLimits.max;

if (!isUnsigned && !fitsSignedLimits) {
if (fitsUnsignedLimits) {
// Find the property after which we should insert the unsigned property
const unsignedIndex = paramInfoPropertyOrder.indexOf(
"unsigned",
);
const insertAfter = node.properties.findLast((p) =>
p.key.type === "JSONLiteral"
&& typeof p.key.value === "string"
&& paramInfoPropertyOrder.indexOf(p.key.value)
< unsignedIndex
);
context.report({
loc: valueSizeProperty.node.loc,
messageId: "incompatible-size",
data: {
minValue: minValue.toString(),
maxValue: maxValue.toString(),
valueSize: valueSize.toString(),
sizeMin: limits.min.toString(),
sizeMax: limits.max.toString(),
},
suggest: [
{
messageId: "convert-to-unsigned",
fix: insertAfter
? insertAfterJSONProperty(
context,
insertAfter,
`"unsigned": true,`,
{ insertComma: true },
)
: insertBeforeJSONProperty(
context,
node.properties[0],
`"unsigned": true,`,
),
},
],
});
} else {
if (minValue < limits.min) {
context.report({
loc: minValueProperty.node.loc,
messageId: "incompatible-min-value",
data: {
minValue: minValue.toString(),
valueSize: valueSize.toString(),
sizeMin: limits.min.toString(),
},
});
}
if (maxValue > limits.max) {
context.report({
loc: maxValueProperty.node.loc,
messageId: "incompatible-max-value",
data: {
maxValue: maxValue.toString(),
valueSize: valueSize.toString(),
sizeMax: limits.max.toString(),
},
});
}
}
} else if (isUnsigned && !fitsUnsignedLimits) {
if (minValue < unsignedLimits.min) {
context.report({
loc: minValueProperty.node.loc,
messageId: "incompatible-min-value",
data: {
minValue: minValue.toString(),
valueSize: valueSize.toString(),
sizeMin: unsignedLimits.min.toString(),
},
});
}
if (maxValue > unsignedLimits.max) {
context.report({
loc: maxValueProperty.node.loc,
messageId: "incompatible-max-value",
data: {
maxValue: maxValue.toString(),
valueSize: valueSize.toString(),
sizeMax: unsignedLimits.max.toString(),
},
});
}
} else if (isUnsigned && fitsSignedLimits) {
context.report({
loc: unsignedProperty.node.loc,
messageId: "unnecessary-unsigned",
fix: removeJSONProperty(
context,
unsignedProperty.node,
),
});
}
},
};
},
meta: {
docs: {
description:
`Ensures that "unsigned = true" is used when necessary and omitted when not.`,
},
fixable: "code",
hasSuggestions: true,
schema: [],
messages: {
"invalid-value-size": "Value size {{valueSize}} is invalid!",
"incompatible-size":
"The defined value range {{minValue}}...{{maxValue}} is incompatible with valueSize {{valueSize}} ({{sizeMin}}...{{sizeMax}})",
"incompatible-min-value":
"minValue {{minValue}} is incompatible with valueSize {{valueSize}} (min = {{sizeMin}})",
"incompatible-max-value":
"maxValue {{maxValue}} is incompatible with valueSize {{valueSize}} (max = {{sizeMax}})",
"convert-to-unsigned": "Convert this parameter to unsigned",
"unnecessary-unsigned":
"Defining this parameter as unsigned is unnecessary",
},
type: "problem",
},
};
Original file line number Diff line number Diff line change
@@ -1,24 +1,6 @@
import { type AST as ESLintAST } from "eslint";
import type { AST } from "jsonc-eslint-parser";
import { type JSONCRule } from "../utils";

const paramInfoPropertyOrder: any[] = [
"#",
"$if",
"$import",
"label",
"description",
"valueSize",
"unit",
"minValue",
"maxValue",
"defaultValue",
"unsigned",
"readOnly",
"writeOnly",
"allowManualEntry",
"options",
];
import { type JSONCRule, paramInfoPropertyOrder } from "../utils";

export const consistentDeviceConfigPropertyOrder: JSONCRule.RuleModule = {
create(context) {
Expand All @@ -35,7 +17,7 @@ export const consistentDeviceConfigPropertyOrder: JSONCRule.RuleModule = {
return undefined;
} else {
return [
paramInfoPropertyOrder.indexOf(p.key.value),
paramInfoPropertyOrder.indexOf(p.key.value as any),
p,
] as const;
}
Expand Down
Loading

0 comments on commit 6b66c18

Please sign in to comment.