From 29b4c1f222278c18b8edc9a71471b20863112dd9 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Fri, 29 Sep 2023 20:59:46 +0200 Subject: [PATCH] chore: enforce title case for device descriptions --- .../src/rules/consistent-config-labels.ts | 101 +++++++++++++++++- 1 file changed, 96 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-config-labels.ts b/packages/eslint-plugin/src/rules/consistent-config-labels.ts index 39a1ad4ff92c..56a8a2bbe100 100644 --- a/packages/eslint-plugin/src/rules/consistent-config-labels.ts +++ b/packages/eslint-plugin/src/rules/consistent-config-labels.ts @@ -1,12 +1,81 @@ import { type AST } from "jsonc-eslint-parser"; import { type JSONCRule } from "../utils"; -// const ROOT = "Program > JSONObjectExpression"; +const ROOT = "Program > JSONExpressionStatement > JSONObjectExpression"; function isSurroundedByWhitespace(str: string) { return /^\s/.test(str) || /\s$/.test(str); } +const titleCaseExceptions = [ + "with", + "in", + "of", + "by", + "for", + "the", + "and", + "kW", + "kWh", + "RFID", +]; + +const titleCaseIgnored: RegExp[] = [ + /^v\d+$/i, // Versions + /^x$/i, // x + /^[a-z]+[A-Z]/, // fancY mArketing nAmEs + /[®™]$/i, // Trademarks etc. +]; + +const alwaysUppercase: RegExp[] = [ + /^\d+\w$/i, +]; + +// TODO: Additional fixes: +// Plug-In, In-Wall +// remove Z-Wave and all its variants + +function isTitleCase(str: string) { + const words = str.split(" "); + return words.every((word, i) => { + if (word.length === 0) return true; + if (i > 0) { + // Exceptions don't apply for the first word + const exception = titleCaseExceptions.find( + (ex) => ex.toLowerCase() === word.toLowerCase(), + ); + if (exception) return word === exception; + } + if (titleCaseIgnored.some((re) => re.test(word))) return true; + if (alwaysUppercase.some((re) => re.test(word))) { + return word === word.toUpperCase(); + } + + return word[0] === word[0].toUpperCase(); + }); +} + +function toTitleCase(str: string) { + const words = str.split(" "); + return words.map((word, i) => { + if (word.length === 0) return word; + // Return some exceptions as they are defined + if (i > 0) { + // Exceptions don't apply for the first word + const exception = titleCaseExceptions.find( + (ex) => ex.toLowerCase() === word.toLowerCase(), + ); + if (exception) return exception; + } + if (titleCaseIgnored.some((re) => re.test(word))) return word; + if (alwaysUppercase.some((re) => re.test(word))) { + return word.toUpperCase(); + } + // Title case the rest + return word[0].toUpperCase() + word.slice(1); + }).join(" "); +} + export const consistentConfigLabels: JSONCRule.RuleModule = { create(context) { if (!context.parserServices.isJSON) { @@ -14,6 +83,7 @@ export const consistentConfigLabels: JSONCRule.RuleModule = { } return { + // Disallow surrounding whitespace JSONLiteral(node: AST.JSONLiteral) { if (typeof node.value !== "string") return; if (!isSurroundedByWhitespace(node.value)) return; @@ -28,11 +98,31 @@ export const consistentConfigLabels: JSONCRule.RuleModule = { ), }); }, - // [`${ROOT} > JSONProperty[key.value='description']`]( - // node: AST.JSONProperty, - // ) { - // }, + // Enforce title case for device descriptions + [`${ROOT} > JSONProperty[key.value='description']`]( + node: AST.JSONProperty, + ) { + if ( + node.value.type !== "JSONLiteral" + || typeof node.value.value !== "string" + ) return; + const value = node.value; + if (isTitleCase(value.value)) return; + + context.report({ + loc: node.loc, + messageId: "must-be-title-case", + data: { + what: "Device descriptions", + }, + fix: (fixer) => + fixer.replaceTextRange( + value.range, + `"${toTitleCase(value.raw.slice(1, -1))}"`, + ), + }); + }, // "JSONProperty[key.value='paramInformation'] > JSONArrayExpression > JSONObjectExpression"( // node: AST.JSONObjectExpression, // ) { @@ -50,6 +140,7 @@ export const consistentConfigLabels: JSONCRule.RuleModule = { messages: { "no-surrounding-whitespace": "Leading and trailing whitespace is not allowed", + "must-be-title-case": "{{what}} must be in Title Case", }, type: "problem", },