From ef5e12ccd85d7a9da61330bea3a19ee988e3e85c Mon Sep 17 00:00:00 2001 From: Olga Bulat Date: Thu, 28 Nov 2024 16:51:10 +0300 Subject: [PATCH] Update casing for Glotpress and simplify ESLint rule --- .vale/styles/Openverse/TermCasing.yml | 11 +- packages/js/eslint-plugin/src/configs/vue.ts | 16 ++- .../src/rules/key-name-casing.ts | 135 ++++++------------ 3 files changed, 61 insertions(+), 101 deletions(-) diff --git a/.vale/styles/Openverse/TermCasing.yml b/.vale/styles/Openverse/TermCasing.yml index db9fc5e17ac..c3231894804 100644 --- a/.vale/styles/Openverse/TermCasing.yml +++ b/.vale/styles/Openverse/TermCasing.yml @@ -12,12 +12,11 @@ swap: # [^/\.] prevents matching things that look like URLs, file paths, or GitHub team mentions # For example: @WordPress/openverse-maintainers '[^/\.]openverse[^/\.]': Openverse - # OpenVerse should never be used, except as an example of something that is always wrong, - # in which case we'll tell Vale to ignore that line. - "OpenVerse": Openverse '[^/\.]wordpress[^/\.]': WordPress - # Wordpress is the same as OpenVerse - "Wordpress": WordPress '[^/\.]github[^/\.]': GitHub - # Github is the same as Wordpress and OpenVerse + # OpenVerse, Wordpress, Github and Glotpress should never be used, except as an example of + # something that is always wrong, in which case we'll tell Vale to ignore that line. + "OpenVerse": Openverse + "Wordpress": WordPress "Github": GitHub + "Glotpress": GlotPress diff --git a/packages/js/eslint-plugin/src/configs/vue.ts b/packages/js/eslint-plugin/src/configs/vue.ts index 0fc6ec6ccb8..17f848a45d8 100644 --- a/packages/js/eslint-plugin/src/configs/vue.ts +++ b/packages/js/eslint-plugin/src/configs/vue.ts @@ -162,8 +162,20 @@ export default tseslint.config( "error", { camelCaseWithDot: true, - snake_case_with_dot: true, // for err_* keys - ignores: ["ncSampling+", "sampling+"], + // keys that still have snake_case_with_dot are in `ignores` to prevent accidentally adding more such mixed keys + snake_case_with_dot: false, + ignores: [ + "ncSampling+", + "sampling+", + "sound_effect", + "digitized_artwork", + "err_network", + "err_decode", + "err_unallowed", + "err_unknown", + "err_unsupported", + "err_aborted", + ], }, ], }, diff --git a/packages/js/eslint-plugin/src/rules/key-name-casing.ts b/packages/js/eslint-plugin/src/rules/key-name-casing.ts index a5277694bbf..fde216b0110 100644 --- a/packages/js/eslint-plugin/src/rules/key-name-casing.ts +++ b/packages/js/eslint-plugin/src/rules/key-name-casing.ts @@ -3,67 +3,38 @@ import { JSONProperty } from "jsonc-eslint-parser/lib/parser/ast" import { OpenverseRule } from "../utils/rule-creator" -export type CasingKind = "camelCaseWithDot" | "snake_case_with_dot" +type CaseFormat = "camelCaseWithDot" | "snake_case_with_dot" -export const allowedCaseOptions: CasingKind[] = [ - "camelCaseWithDot", - "snake_case_with_dot", -] - -const checkersMap = { - camelCaseWithDot: isCamelCase, - snake_case_with_dot: isSnakeCase, -} - -/** - * Checks whether the given string has symbols. - */ -function hasSymbols(str: string) { - return /[\u0021-\u0023\u0025-\u002c./\u003a-\u0040\u005b-\u005e`\u007b-\u007d]/u.test( - str - ) // without " ", "$", "-" and "_" -} /** - * Checks whether the given string has upper. + * Validates if a string matches the specified case format */ -function hasUpper(str: string) { - return /[A-Z]/u.test(str) -} -/** - * Checks whether the given string is camelCase. - */ -export function isCamelCase(str: string): boolean { - return !(hasSymbols(str) || /^[A-Z]/u.test(str) || /[\s\-_]/u.test(str)) -} +function isValidCase(str: string, format: CaseFormat): boolean { + const patterns = { + camelCaseWithDot: /^[a-z][a-zA-Z0-9]*$/, + snake_case_with_dot: /^[a-z0-9]+(_[a-z0-9]+)*$/, + } -/** - * Checks whether the given string is snake_case. - */ -export function isSnakeCase(str: string): boolean { - return !(hasUpper(str) || hasSymbols(str) || /-|__|\s/u.test(str)) -} - -/** - * Return case checker ('camelCaseWithDot', 'snake_case_with_dot') - */ -export function getChecker(name: "camelCaseWithDot"): (str: string) => boolean { - return checkersMap[name] + if (str.includes(".")) { + return str.split(".").every((part) => { + if (/^\d+$/.test(part)) { + return true + } + return patterns[format].test(part) + }) + } + return patterns[format].test(str) } -type Option = { - [key in CasingKind]?: boolean -} & { +type RuleOptions = { + camelCaseWithDot?: boolean + snake_case_with_dot?: boolean ignores?: string[] } -type MessageIds = "incorrectKeyNameComment" - -const messages = { - incorrectKeyNameComment: - "Property name `{{name}}` must match one of the following formats: {{formats}}", -} as const - -export const keyNameCasing = OpenverseRule({ +export const keyNameCasing = OpenverseRule< + [RuleOptions], + "incorrectKeyNameComment" +>({ name: "key-name-casing", meta: { docs: { @@ -73,27 +44,21 @@ export const keyNameCasing = OpenverseRule({ { type: "object", properties: { - camelCaseWithDot: { - type: "boolean", - default: true, - }, - snake_case_with_dot: { - type: "boolean", - default: true, - }, + camelCaseWithDot: { type: "boolean", default: true }, + snake_case_with_dot: { type: "boolean", default: true }, ignores: { type: "array", - items: { - type: "string", - }, + items: { type: "string" }, uniqueItems: true, - additionalItems: false, }, }, additionalProperties: false, }, ], - messages, + messages: { + incorrectKeyNameComment: + "Property name `{{name}}` must match one of the following formats: {{formats}}", + }, type: "suggestion", }, defaultOptions: [ @@ -107,36 +72,19 @@ export const keyNameCasing = OpenverseRule({ if (!(sourceCode.parserServices as SourceCode.ParserServices).isJSON) { return {} } - const option: Option = { ...context.options[0] } - if (option.camelCaseWithDot !== false) { - option.camelCaseWithDot = true - } - const ignores = option.ignores - ? option.ignores.map((ignore) => new RegExp(ignore)) - : [] - const formats = Object.keys(option) - .filter((key): key is "camelCaseWithDot" => - allowedCaseOptions.includes(key as "camelCaseWithDot") - ) - .filter((key) => option[key]) - const checkers: ((str: string) => boolean)[] = formats.map(getChecker) + const options = { ...context.options[0] } + const ignores = options.ignores?.map((pattern) => new RegExp(pattern)) || [] + const enabledFormats = ( + ["camelCaseWithDot", "snake_case_with_dot"] as const + ).filter((format) => options[format] !== false) - /** - * Check whether a given name is a valid. - */ - function isValid(name: string): boolean { - if (ignores.some((regex) => regex.test(name))) { - return true - } - if (!checkers.length) { + function isValidName(name: string): boolean { + if (!enabledFormats.length || ignores.some((regex) => regex.test(name))) { return true } - if (name.includes(".")) { - const parts = name.split(".") - return parts.every((part) => checkers.some((c) => c(part))) - } - return checkers.length ? checkers.some((c) => c(name)) : true + + return enabledFormats.some((format) => isValidCase(name, format)) } return { @@ -145,13 +93,14 @@ export const keyNameCasing = OpenverseRule({ node.key.type === "JSONLiteral" && typeof node.key.value === "string" ? node.key.value : sourceCode.text.slice(...node.key.range) - if (!isValid(name)) { + + if (!isValidName(name)) { context.report({ loc: node.key.loc, messageId: "incorrectKeyNameComment", data: { name, - formats: formats.join(", "), + formats: enabledFormats.join(", "), }, }) }