diff --git a/eslint.config.js b/eslint.config.js index deb38966..fc58aba8 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,6 +1,7 @@ /* eslint-disable n/no-extraneous-import */ import js from "@eslint/js"; +import keybr from "@keybr/scripts/lib/eslint-plugin-keybr.js"; import confusingBrowserGlobals from "confusing-browser-globals"; import ava from "eslint-plugin-ava"; import formatjs from "eslint-plugin-formatjs"; @@ -32,6 +33,7 @@ export default [ react.configs.flat["jsx-runtime"], node.configs["flat/recommended-module"], ava.configs["flat/recommended"], + keybr.configs["recommended"], { ignores: ["packages/server/**", "packages/server-cli/**"], plugins: { "react-hooks": reactHooks }, @@ -87,10 +89,7 @@ export default [ // configure node "n/file-extension-in-import": ["error", "always"], "n/hashbang": "off", - "n/no-path-concat": "off", "n/no-process-exit": "off", - "n/no-unsupported-features/es-builtins": "off", - "n/no-unsupported-features/es-syntax": "off", "n/no-unsupported-features/node-builtins": "off", "n/prefer-global/buffer": ["error", "always"], "n/prefer-global/console": ["error", "always"], diff --git a/packages/keybr-generators/lib/layout/diacritics.ts b/packages/keybr-generators/lib/layout/diacritics.ts index 8ff39e6c..57ee6b7c 100644 --- a/packages/keybr-generators/lib/layout/diacritics.ts +++ b/packages/keybr-generators/lib/layout/diacritics.ts @@ -46,7 +46,7 @@ export const makeDeadCharacter = ( if (dead != null) { return dead; } - if (codePoint === /* * */ 0x002a) { + if (codePoint === /* "*" */ 0x002a) { return { dead: codePoint }; } console.error( diff --git a/packages/keybr-generators/lib/layout/import-json.ts b/packages/keybr-generators/lib/layout/import-json.ts index 702aead2..a76c569c 100644 --- a/packages/keybr-generators/lib/layout/import-json.ts +++ b/packages/keybr-generators/lib/layout/import-json.ts @@ -70,7 +70,7 @@ function parseCharacterList( } if (a.length === 2) { - if (a[0] === /* * */ 0x002a) { + if (a[0] === /* "*" */ 0x002a) { characters.push(makeDeadCharacter(keyId, a[1])); continue; } diff --git a/packages/keybr-keyboard-ui/lib/Key.test.tsx b/packages/keybr-keyboard-ui/lib/Key.test.tsx index 7c5fb0dc..f553a310 100644 --- a/packages/keybr-keyboard-ui/lib/Key.test.tsx +++ b/packages/keybr-keyboard-ui/lib/Key.test.tsx @@ -63,8 +63,8 @@ test.serial("dead labels", (t) => { [ { dead: /* COMBINING GRAVE ACCENT */ 0x0300 }, { dead: /* COMBINING ACUTE ACCENT */ 0x0301 }, - { dead: /* * */ 0x002a }, - { dead: /* * */ 0x002a }, + { dead: /* "*" */ 0x002a }, + { dead: /* "*" */ 0x002a }, ], ); diff --git a/packages/keybr-keyboard/lib/keyboard.test.ts b/packages/keybr-keyboard/lib/keyboard.test.ts index 12b9d40d..6b417b97 100644 --- a/packages/keybr-keyboard/lib/keyboard.test.ts +++ b/packages/keybr-keyboard/lib/keyboard.test.ts @@ -24,8 +24,8 @@ test("data", (t) => { Equal: [ { dead: /* COMBINING ACUTE ACCENT */ 0x0301 }, { dead: /* COMBINING GRAVE ACCENT */ 0x0300 }, - { dead: /* * */ 0x002a }, - { dead: /* * */ 0x002a }, + { dead: /* "*" */ 0x002a }, + { dead: /* "*" */ 0x002a }, ], }; const geometryDict: GeometryDict = { @@ -124,8 +124,8 @@ test("data", (t) => { "Equal", { dead: /* COMBINING ACUTE ACCENT */ 0x0301 }, { dead: /* COMBINING GRAVE ACCENT */ 0x0300 }, - { dead: /* * */ 0x002a }, - { dead: /* * */ 0x002a }, + { dead: /* "*" */ 0x002a }, + { dead: /* "*" */ 0x002a }, ), ); diff --git a/packages/keybr-keyboard/lib/keycharacters.test.ts b/packages/keybr-keyboard/lib/keycharacters.test.ts index d2082ab3..48ada223 100644 --- a/packages/keybr-keyboard/lib/keycharacters.test.ts +++ b/packages/keybr-keyboard/lib/keycharacters.test.ts @@ -19,8 +19,8 @@ test("codepoint characters", (t) => { test("dead characters", (t) => { const characters = new KeyCharacters( "KeyA", - { dead: /* * */ 0x002a }, - { dead: /* * */ 0x002a }, + { dead: /* "*" */ 0x002a }, + { dead: /* "*" */ 0x002a }, null, null, ); diff --git a/packages/keybr-unicode/lib/whitespace.ts b/packages/keybr-unicode/lib/whitespace.ts index acb9420c..b5294035 100644 --- a/packages/keybr-unicode/lib/whitespace.ts +++ b/packages/keybr-unicode/lib/whitespace.ts @@ -7,7 +7,6 @@ export const isLinebreak = (codePoint: CodePoint): boolean => codePoint === /* LINE TABULATION */ 0x000b || codePoint === /* FORM FEED */ 0x000c || codePoint === /* CARRIAGE RETURN */ 0x000d || - codePoint === /* NEXT LINE */ 0x0085 || codePoint === /* LINE SEPARATOR */ 0x2028 || codePoint === /* PARAGRAPH SEPARATOR */ 0x2029; @@ -18,7 +17,6 @@ export const isWhitespace = (codePoint: CodePoint): boolean => codePoint === /* FORM FEED */ 0x000c || codePoint === /* CARRIAGE RETURN */ 0x000d || codePoint === /* SPACE */ 0x0020 || - codePoint === /* NEXT LINE */ 0x0085 || codePoint === /* NO-BREAK SPACE */ 0x00a0 || codePoint === /* EN QUAD */ 0x2000 || codePoint === /* EM QUAD */ 0x2001 || diff --git a/scripts/config-lint.js b/scripts/config-lint.js index 60f25527..4dcc59b2 100644 --- a/scripts/config-lint.js +++ b/scripts/config-lint.js @@ -1,7 +1,7 @@ import { existsSync } from "node:fs"; import { join } from "node:path"; import { globSync } from "glob"; -import { readJsonSync, writeJsonSync } from "./lib/fs.js"; +import { readJsonSync, writeJsonSync } from "./lib/fs-json.js"; import { packageJsonKeys, tsconfigJsonKeys } from "./lib/key-order.js"; import { findDeps, printUnusedDeps } from "./lib/lang.js"; import { sortJson } from "./lib/sort-json.js"; diff --git a/scripts/lib/codepoints.js b/scripts/lib/codepoints.js new file mode 100644 index 00000000..4f18c57c --- /dev/null +++ b/scripts/lib/codepoints.js @@ -0,0 +1,16 @@ +import unicodeNames from "@unicode/unicode-16.0.0/Names/index.js"; + +const controlNames = new Map([ + [0x0000, "NULL"], + [0x0008, "BACKSPACE"], + [0x0009, "CHARACTER TABULATION"], + [0x000a, "LINE FEED"], + [0x000b, "LINE TABULATION"], + [0x000c, "FORM FEED"], + [0x000d, "CARRIAGE RETURN"], + [0x001b, "ESCAPE"], +]); + +export function getCodePointName(codePoint) { + return controlNames.get(codePoint) ?? unicodeNames.get(codePoint) ?? "?"; +} diff --git a/scripts/lib/eslint-plugin-keybr.js b/scripts/lib/eslint-plugin-keybr.js new file mode 100644 index 00000000..d227e51e --- /dev/null +++ b/scripts/lib/eslint-plugin-keybr.js @@ -0,0 +1,72 @@ +import { getCodePointName } from "./codepoints.js"; + +const rule = { + meta: { + type: "problem", + docs: { + description: "Checks code point name comments.", + }, + fixable: "code", + schema: [], + }, + create(context) { + return { + Literal(node) { + if ( + node.type === "Literal" && + typeof node.value === "number" && + /0x[0-9a-fA-F]{4}/.test(node.raw) + ) { + const [comment] = context.sourceCode.getCommentsBefore(node); + if (comment?.type === "Block") { + const name1 = + node.value === /* QUOTATION MARK */ 0x0022 + ? ` '"' ` + : ` "${String.fromCodePoint(node.value)}" `; + const name2 = ` ${getCodePointName(node.value)} `; + if (comment.value !== name1 && comment.value !== name2) { + const expected = comment.value.includes('"') + ? `/*${name1}*/` + : `/*${name2}*/`; + context.report({ + node, + message: + "Invalid code point name comment, expected: {{ expected }}", + data: { + expected, + }, + fix(fixer) { + return fixer.replaceText(comment, expected); + }, + }); + } + } + } + }, + }; + }, +}; + +const plugin = { + meta: { + name: "eslint-plugin-keybr", + version: "0.0.0", + }, + configs: {}, + rules: { + "codepoint-names": rule, + }, +}; + +Object.assign(plugin.configs, { + recommended: { + plugins: { + keybr: plugin, + }, + rules: { + "keybr/codepoint-names": "error", + }, + }, +}); + +export default plugin; diff --git a/scripts/lib/fs.js b/scripts/lib/fs-json.js similarity index 100% rename from scripts/lib/fs.js rename to scripts/lib/fs-json.js diff --git a/scripts/translate.js b/scripts/translate.js index 1b24b1c0..29c14dae 100644 --- a/scripts/translate.js +++ b/scripts/translate.js @@ -2,7 +2,7 @@ import { writeFileSync } from "node:fs"; import { join } from "node:path"; import { compile, extract } from "@formatjs/cli-lib"; import { globSync } from "glob"; -import { readJsonSync, writeJsonSync } from "./lib/fs.js"; +import { readJsonSync, writeJsonSync } from "./lib/fs-json.js"; import { getHashDigest } from "./lib/intl.js"; import { findPackages, rootDir } from "./root.js";