diff --git a/.githooks/pre-commit b/.githooks/pre-commit index e874da3..0840424 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,2 +1,2 @@ #!/bin/sh -npx --no-install lint-staged +npx --no-install lint-staged \ No newline at end of file diff --git a/.textlintrc.json b/.textlintrc.json index 6fe4334..9b78e0d 100644 --- a/.textlintrc.json +++ b/.textlintrc.json @@ -1,7 +1,7 @@ { - "plugins": {}, - "filters": {}, - "rules": { - "preset-japanese": true - } -} \ No newline at end of file + "plugins": {}, + "filters": {}, + "rules": { + "preset-japanese": true + } +} diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..5e519aa --- /dev/null +++ b/biome.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.0.6/schema.json", + "files": { + "ignoreUnknown": false, + "includes": ["src/**/*", "test/**/*", "*.js", "*.ts", "*.json"] + }, + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 120, + "lineEnding": "lf" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "error", + "noImplicitAnyLet": "error", + "noAssignInExpressions": "error", + "noMisleadingCharacterClass": "error" + }, + "style": { + "noNonNullAssertion": "error" + }, + "correctness": { + "noUnusedVariables": "error" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "semicolons": "always", + "trailingCommas": "none" + } + }, + "json": { + "formatter": { + "enabled": true + } + } +} diff --git a/package-lock.json b/package-lock.json index 4052d4f..c6f5311 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,11 @@ "textlint-util-to-string": "^3.3.4" }, "devDependencies": { + "@biomejs/biome": "^2.0.6", "@textlint/types": "^14.8.4", "@types/node": "^24.0.1", + "husky": "^9.1.7", "lint-staged": "^16.1.2", - "prettier": "^3.5.3", "textlint": "^14.8.4", "textlint-rule-preset-japanese": "^10.0.4", "textlint-scripts": "^14.8.4", @@ -1475,6 +1476,169 @@ "node": ">=6.9.0" } }, + "node_modules/@biomejs/biome": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.0.6.tgz", + "integrity": "sha512-RRP+9cdh5qwe2t0gORwXaa27oTOiQRQvrFf49x2PA1tnpsyU7FIHX4ZOFMtBC4QNtyWsN7Dqkf5EDbg4X+9iqA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.0.6", + "@biomejs/cli-darwin-x64": "2.0.6", + "@biomejs/cli-linux-arm64": "2.0.6", + "@biomejs/cli-linux-arm64-musl": "2.0.6", + "@biomejs/cli-linux-x64": "2.0.6", + "@biomejs/cli-linux-x64-musl": "2.0.6", + "@biomejs/cli-win32-arm64": "2.0.6", + "@biomejs/cli-win32-x64": "2.0.6" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.0.6.tgz", + "integrity": "sha512-AzdiNNjNzsE6LfqWyBvcL29uWoIuZUkndu+wwlXW13EKcBHbbKjNQEZIJKYDc6IL+p7bmWGx3v9ZtcRyIoIz5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.0.6.tgz", + "integrity": "sha512-wJjjP4E7bO4WJmiQaLnsdXMa516dbtC6542qeRkyJg0MqMXP0fvs4gdsHhZ7p9XWTAmGIjZHFKXdsjBvKGIJJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.0.6.tgz", + "integrity": "sha512-ZSVf6TYo5rNMUHIW1tww+rs/krol7U5A1Is/yzWyHVZguuB0lBnIodqyFuwCNqG9aJGyk7xIMS8HG0qGUPz0SA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.6.tgz", + "integrity": "sha512-CVPEMlin3bW49sBqLBg2x016Pws7eUXA27XYDFlEtponD0luYjg2zQaMJ2nOqlkKG9fqzzkamdYxHdMDc2gZFw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.0.6.tgz", + "integrity": "sha512-geM1MkHTV1Kh2Cs/Xzot9BOF3WBacihw6bkEmxkz4nSga8B9/hWy5BDiOG3gHDGIBa8WxT0nzsJs2f/hPqQIQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.6.tgz", + "integrity": "sha512-mKHE/e954hR/hSnAcJSjkf4xGqZc/53Kh39HVW1EgO5iFi0JutTN07TSjEMg616julRtfSNJi0KNyxvc30Y4rQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.0.6.tgz", + "integrity": "sha512-290V4oSFoKaprKE1zkYVsDfAdn0An5DowZ+GIABgjoq1ndhvNxkJcpxPsiYtT7slbVe3xmlT0ncdfOsN7KruzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.0.6.tgz", + "integrity": "sha512-bfM1Bce0d69Ao7pjTjUS+AWSZ02+5UHdiAP85Th8e9yV5xzw6JrHXbL5YWlcEKQ84FIZMdDc7ncuti1wd2sdbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "dev": true, @@ -3762,6 +3926,22 @@ "node": ">= 0.8" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -5687,22 +5867,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/property-information": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", diff --git a/package.json b/package.json index 616748c..07d0b87 100644 --- a/package.json +++ b/package.json @@ -1,76 +1,78 @@ { - "name": "@textlint-ja/textlint-rule-preset-ai-writing", - "version": "1.5.0", - "description": "textlintプリセット:AIっぽい記述パターンを検出し、より自然な日本語表現を促すルール集", - "keywords": [ - "textlintrule", - "textlint-rule-preset", - "japanese", - "ai-writing", - "natural-writing", - "preset" - ], - "homepage": "https://github.com/textlint-ja/textlint-rule-preset-ai-writing", - "bugs": { - "url": "https://github.com/textlint-ja/textlint-rule-preset-ai-writing/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/textlint-ja/textlint-rule-preset-ai-writing.git" - }, - "license": "MIT", - "author": "azu", - "main": "lib/index.js", - "directories": { - "lib": "lib", - "test": "test" - }, - "files": [ - "bin/", - "lib/", - "src/" - ], - "scripts": { - "build": "textlint-scripts build", - "format": "prettier --write \"**/*.{js,jsx,ts,tsx,css}\"", - "prepare": "git config --local core.hooksPath .githooks", - "prepublishOnly": "npm run build", - "test": "npm run test:unit && npm run lint:text && npm run lint:README", - "lint:text": "textlint README.md", - "lint:README": "bash lint-README.sh", - "test:unit": "textlint-scripts test", - "watch": "textlint-scripts build --watch" - }, - "lint-staged": { - "*.{js,jsx,ts,tsx,css}": [ - "prettier --write" - ] - }, - "prettier": { - "singleQuote": false, - "printWidth": 120, - "tabWidth": 4, - "trailingComma": "none" - }, - "devDependencies": { - "@textlint/types": "^14.8.4", - "@types/node": "^24.0.1", - "lint-staged": "^16.1.2", - "prettier": "^3.5.3", - "textlint": "^14.8.4", - "textlint-rule-preset-japanese": "^10.0.4", - "textlint-scripts": "^14.8.4", - "textlint-tester": "^14.8.4", - "ts-node": "^10.9.2", - "typescript": "^5.8.3" - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@textlint/regexp-string-matcher": "^2.0.2", - "kuromojin": "^3.0.1", - "textlint-util-to-string": "^3.3.4" - }, - "packageManager": "npm@10.9.2+sha512.8ab88f10f224a0c614cb717a7f7c30499014f77134120e9c1f0211ea3cf3397592cbe483feb38e0c4b3be1c54e347292c76a1b5edb94a3289d5448484ab8ac81" + "name": "@textlint-ja/textlint-rule-preset-ai-writing", + "version": "1.5.0", + "description": "textlintプリセット:AIっぽい記述パターンを検出し、より自然な日本語表現を促すルール集", + "keywords": [ + "textlintrule", + "textlint-rule-preset", + "japanese", + "ai-writing", + "natural-writing", + "preset" + ], + "homepage": "https://github.com/textlint-ja/textlint-rule-preset-ai-writing", + "bugs": { + "url": "https://github.com/textlint-ja/textlint-rule-preset-ai-writing/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/textlint-ja/textlint-rule-preset-ai-writing.git" + }, + "license": "MIT", + "author": "azu", + "main": "lib/index.js", + "directories": { + "lib": "lib", + "test": "test" + }, + "files": [ + "bin/", + "lib/", + "src/" + ], + "scripts": { + "build": "textlint-scripts build", + "format": "biome check --write ./", + "lint": "biome check ./", + "prepare": "git config --local core.hooksPath .githooks", + "prepublishOnly": "npm run build", + "test": "npm run test:unit && npm run lint && npm run lint:text && npm run lint:README", + "lint:text": "textlint README.md", + "lint:README": "bash lint-README.sh", + "test:unit": "textlint-scripts test", + "watch": "textlint-scripts build --watch" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx,css}": [ + "biome format --write --no-errors-on-unmatched" + ] + }, + "prettier": { + "singleQuote": false, + "printWidth": 120, + "tabWidth": 4, + "trailingComma": "none" + }, + "devDependencies": { + "@biomejs/biome": "^2.0.6", + "@textlint/types": "^14.8.4", + "@types/node": "^24.0.1", + "husky": "^9.1.7", + "lint-staged": "^16.1.2", + "textlint": "^14.8.4", + "textlint-rule-preset-japanese": "^10.0.4", + "textlint-scripts": "^14.8.4", + "textlint-tester": "^14.8.4", + "ts-node": "^10.9.2", + "typescript": "^5.8.3" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@textlint/regexp-string-matcher": "^2.0.2", + "kuromojin": "^3.0.1", + "textlint-util-to-string": "^3.3.4" + }, + "packageManager": "npm@10.9.2+sha512.8ab88f10f224a0c614cb717a7f7c30499014f77134120e9c1f0211ea3cf3397592cbe483feb38e0c4b3be1c54e347292c76a1b5edb94a3289d5448484ab8ac81" } diff --git a/src/index.ts b/src/index.ts index 407ffe6..9fb85ed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ -import noAiListFormatting from "./rules/no-ai-list-formatting"; -import noAiHypeExpressions from "./rules/no-ai-hype-expressions"; -import noAiEmphasisPatterns from "./rules/no-ai-emphasis-patterns"; import aiTechWritingGuideline from "./rules/ai-tech-writing-guideline"; import noAiColonContinuation from "./rules/no-ai-colon-continuation"; +import noAiEmphasisPatterns from "./rules/no-ai-emphasis-patterns"; +import noAiHypeExpressions from "./rules/no-ai-hype-expressions"; +import noAiListFormatting from "./rules/no-ai-list-formatting"; const preset = { rules: { diff --git a/src/rules/ai-tech-writing-guideline.ts b/src/rules/ai-tech-writing-guideline.ts index aeda9b5..1dcb543 100644 --- a/src/rules/ai-tech-writing-guideline.ts +++ b/src/rules/ai-tech-writing-guideline.ts @@ -1,5 +1,6 @@ -import type { TextlintRuleModule } from "@textlint/types"; +import type { AnyTxtNode } from "@textlint/ast-node-types"; import { matchPatterns } from "@textlint/regexp-string-matcher"; +import type { TextlintRuleContext, TextlintRuleModule } from "@textlint/types"; import { StringSource } from "textlint-util-to-string"; /** @@ -9,7 +10,7 @@ import { StringSource } from "textlint-util-to-string"; * https://github.com/textlint-ja/textlint-rule-preset-ai-writing/blob/main/docs/tech-writing-guidelines.md */ -export interface Options { +type Options = { // If node's text includes allowed patterns, does not report. // Can be string or RegExp-like string ("/pattern/flags") allows?: string[]; @@ -21,9 +22,9 @@ export interface Options { disableStructureGuidance?: boolean; // Enable document-level analysis enableDocumentAnalysis?: boolean; -} +}; -const rule: TextlintRuleModule = (context, options = {}) => { +const rule: TextlintRuleModule = (context: TextlintRuleContext, options = {}) => { const { Syntax, report, locator } = context; const allows = options.allows ?? []; const disableRedundancyGuidance = options.disableRedundancyGuidance ?? false; @@ -190,8 +191,9 @@ const rule: TextlintRuleModule = (context, options = {}) => { * 機械的な段落と箇条書きの組み合わせパターンを検出 * 注意: コロン関連のパターンは no-ai-colon-continuation ルールで処理されます */ - const detectMechanicalListIntroPattern = (node: any) => { - const children = node.children || []; + const detectMechanicalListIntroPattern = (node: AnyTxtNode) => { + const nodeObj = node as AnyTxtNode & { children?: AnyTxtNode[] }; + const children = nodeObj.children || []; for (let i = 0; i < children.length - 1; i++) { const currentNode = children[i]; @@ -200,7 +202,7 @@ const rule: TextlintRuleModule = (context, options = {}) => { // Paragraph → List のパターンを検出 if (currentNode.type === "Paragraph" && nextNode.type === "List") { // Paragraphの最後の文字列ノードを取得 - const paragraphSource = new StringSource(currentNode, { + const paragraphSource = new StringSource(currentNode as never, { replacer({ node, emptyValue }) { if (node.type === "Code" || node.type === "InlineCode") { return emptyValue(); @@ -217,7 +219,9 @@ const rule: TextlintRuleModule = (context, options = {}) => { // 注意: コロンパターンは no-ai-colon-continuation で処理されるため削除 // パターン: 「例えば。」「具体的には。」など、接続表現+句点で終わる段落 - if (/(?:例えば|具体的には|詳細には|以下|次に|また)。[\s]*$/.test(paragraphText.trim())) { + const connectivePattern = /(?:例えば|具体的には|詳細には|以下|次に|また)。[\s]*$/; + const connectiveMatch = paragraphText.trim().match(connectivePattern); + if (connectiveMatch) { isDetected = true; message = "【構造化】接続表現と句点で終わる文の直後の箇条書きは機械的な印象を与える可能性があります。「たとえば、次のような点があります。」のような自然な導入文を検討してください。"; @@ -246,7 +250,7 @@ const rule: TextlintRuleModule = (context, options = {}) => { /** * 構造化ガイダンスに関する文書レベルの検出処理 */ - const processDocumentStructureGuidance = (node: any) => { + const processDocumentStructureGuidance = (node: AnyTxtNode) => { if (disableStructureGuidance) { return; } @@ -265,9 +269,9 @@ const rule: TextlintRuleModule = (context, options = {}) => { /** * 段落内のガイダンスパターンを検出・報告 */ - const processParagraphGuidance = (node: any) => { + const processParagraphGuidance = (node: AnyTxtNode) => { // StringSourceを使用してコードブロックを除外したテキストを取得 - const source = new StringSource(node, { + const source = new StringSource(node as never, { replacer({ node, emptyValue }) { // コードブロック、インラインコードを除外 if (node.type === "Code" || node.type === "InlineCode") { @@ -298,13 +302,13 @@ const rule: TextlintRuleModule = (context, options = {}) => { for (const { pattern, message, category } of allGuidancePatterns) { const matches = text.matchAll(pattern); for (const match of matches) { - const index = match.index ?? 0; + const index: number = match.index ?? 0; // プレーンテキストの位置を元のノード内の位置に変換 const originalIndex = source.originalIndexFromIndex(index); const originalEndIndex = source.originalIndexFromIndex(index + match[0].length); - if (originalIndex !== undefined && originalEndIndex !== undefined) { + if (typeof originalIndex === "number" && typeof originalEndIndex === "number") { const originalRange = [originalIndex, originalEndIndex] as const; // カテゴリ別のメトリクスを更新 @@ -323,7 +327,7 @@ const rule: TextlintRuleModule = (context, options = {}) => { /** * 文書全体の品質分析結果を報告 */ - const processDocumentAnalysis = (node: any) => { + const processDocumentAnalysis = (node: AnyTxtNode) => { // 文書全体の分析を実行(enableDocumentAnalysisがtrueの場合) if (enableDocumentAnalysis && hasDocumentIssues) { const totalIssues = Object.values(documentQualityMetrics).reduce((sum, count) => sum + count, 0); diff --git a/src/rules/no-ai-colon-continuation.ts b/src/rules/no-ai-colon-continuation.ts index bab6769..bec9196 100644 --- a/src/rules/no-ai-colon-continuation.ts +++ b/src/rules/no-ai-colon-continuation.ts @@ -1,6 +1,16 @@ +import type { AnyTxtNode } from "@textlint/ast-node-types"; import { matchPatterns } from "@textlint/regexp-string-matcher"; -import { StringSource } from "textlint-util-to-string"; +import type { TextlintRuleContext, TextlintRuleModule } from "@textlint/types"; import { tokenize } from "kuromojin"; +import { StringSource } from "textlint-util-to-string"; + +type Options = { + allows?: string[]; + disableCodeBlock?: boolean; + disableList?: boolean; + disableQuote?: boolean; + disableTable?: boolean; +}; /** * コロンの直後にブロック要素が続くパターンを検出するルール @@ -15,7 +25,7 @@ import { tokenize } from "kuromojin"; * - 「次のように使用します」 * - 「以下の手順で実行してください」 */ -const rule = (context: any, options: any = {}) => { +const rule: TextlintRuleModule = (context: TextlintRuleContext, options: Options = {}) => { const { Syntax, RuleError, report, getSource, locator } = context; const allows = options.allows ?? []; const disableCodeBlock = options.disableCodeBlock ?? false; @@ -25,7 +35,7 @@ const rule = (context: any, options: any = {}) => { // AST走査で隣接するノードの組み合わせをチェック - const checkColonContinuation = async (paragraphNode: any, nextNode: any) => { + const checkColonContinuation = async (paragraphNode: AnyTxtNode, nextNode: AnyTxtNode) => { // Paragraphノードのテキストを取得 const paragraphText = getSource(paragraphNode); @@ -43,7 +53,7 @@ const rule = (context: any, options: any = {}) => { } // StringSourceを使ってMarkdownを取り除いたテキストを取得 - const stringSource = new StringSource(paragraphNode); + const stringSource = new StringSource(paragraphNode as never); const plainText = stringSource.toString().trim(); // プレーンテキストでもコロン(半角・全角)で終わっているかチェック @@ -102,7 +112,7 @@ const rule = (context: any, options: any = {}) => { // その他の品詞(助詞等)の場合は文脈による // より保守的にエラーとする return false; - } catch (error) { + } catch (_error) { // 形態素解析でエラーが発生した場合は許可(保守的にエラーを避ける) return true; } @@ -146,7 +156,7 @@ const rule = (context: any, options: any = {}) => { }; return { - async [Syntax.Document](node: any) { + async [Syntax.Document](node: AnyTxtNode & { children: AnyTxtNode[] }) { // ドキュメントの子ノードを順番にチェック for (const [i, currentNode] of node.children.entries()) { const nextNode = node.children[i + 1]; diff --git a/src/rules/no-ai-emphasis-patterns.ts b/src/rules/no-ai-emphasis-patterns.ts index 8e0fb0d..9fcc3f7 100644 --- a/src/rules/no-ai-emphasis-patterns.ts +++ b/src/rules/no-ai-emphasis-patterns.ts @@ -1,7 +1,7 @@ -import type { TextlintRuleModule } from "@textlint/types"; import { matchPatterns } from "@textlint/regexp-string-matcher"; +import type { TextlintRuleContext, TextlintRuleModule } from "@textlint/types"; -export interface Options { +type Options = { // 指定したパターンにマッチする場合、エラーを報告しません // 文字列または正規表現パターン ("/pattern/flags") で指定可能 allows?: string[]; @@ -9,9 +9,9 @@ export interface Options { disableEmojiEmphasisPatterns?: boolean; // 情報系プレフィックスパターンの検出を無効にする disableInfoPatterns?: boolean; -} +}; -const rule: TextlintRuleModule = (context, options = {}) => { +const rule: TextlintRuleModule = (context: TextlintRuleContext, options = {}) => { const { Syntax, RuleError, report, getSource, locator } = context; const allows = options.allows ?? []; const disableEmojiEmphasisPatterns = options.disableEmojiEmphasisPatterns ?? false; @@ -56,17 +56,17 @@ const rule: TextlintRuleModule = (context, options = {}) => { return; } - let emojiEmphasizeMatches: RegExpExecArray[] = []; + const emojiEmphasizeMatches: RegExpMatchArray[] = []; // 絵文字 + 太字の組み合わせパターンを検出 if (!disableEmojiEmphasisPatterns) { - // 絵文字の正規表現を修正(サロゲートペア対応) - const emojiEmphasizePattern = /([ℹ️🔍✅❌⚠️💡📝📋📌🔗🎯🚀⭐✨💯🔥📊📈])\s*\*\*([^*]+)\*\*/g; + // 絵文字の正規表現を修正(Unicodeフラグでサロゲートペア対応) + const emojiEmphasizePattern = + /(ℹ️|🔍|✅|❌|⚠️|💡|📝|📋|📌|🔗|🎯|🚀|⭐|✨|💯|🔥|📊|📈)\s*\*\*([^*]+)\*\*/gu; - let match; - while ((match = emojiEmphasizePattern.exec(text)) !== null) { - const matchStart = match.index; - const matchEnd = match.index + match[0].length; + for (const match of text.matchAll(emojiEmphasizePattern)) { + const matchStart = match.index ?? 0; + const matchEnd = matchStart + match[0].length; emojiEmphasizeMatches.push(match); @@ -86,16 +86,15 @@ const rule: TextlintRuleModule = (context, options = {}) => { if (!disableInfoPatterns) { const infoPrefixPattern = new RegExp(`\\*\\*(${infoPatterns.join("|")})([::].*?)?\\*\\*`, "g"); - let match; - while ((match = infoPrefixPattern.exec(text)) !== null) { - const matchStart = match.index; - const matchEnd = match.index + match[0].length; + for (const match of text.matchAll(infoPrefixPattern)) { + const matchStart = match.index ?? 0; + const matchEnd = matchStart + match[0].length; const prefixText = match[1]; // 絵文字+太字のマッチと重複していないかチェック const isOverlapping = emojiEmphasizeMatches.some((emojiMatch) => { - const emojiStart = emojiMatch.index!; - const emojiEnd = emojiMatch.index! + emojiMatch[0].length; + const emojiStart = emojiMatch.index ?? 0; + const emojiEnd = emojiStart + emojiMatch[0].length; return matchStart < emojiEnd && matchEnd > emojiStart; }); @@ -125,16 +124,16 @@ const rule: TextlintRuleModule = (context, options = {}) => { } } - let emojiEmphasizeMatches: RegExpExecArray[] = []; + const emojiEmphasizeMatches: RegExpMatchArray[] = []; // リストアイテム内での絵文字 + 太字パターンを検出 if (!disableEmojiEmphasisPatterns) { - const emojiEmphasizePattern = /([ℹ️🔍✅❌⚠️💡📝📋📌🔗🎯🚀⭐✨💯🔥📊📈])\s*\*\*([^*]+)\*\*/g; + const emojiEmphasizePattern = + /(ℹ️|🔍|✅|❌|⚠️|💡|📝|📋|📌|🔗|🎯|🚀|⭐|✨|💯|🔥|📊|📈)\s*\*\*([^*]+)\*\*/gu; - let match; - while ((match = emojiEmphasizePattern.exec(text)) !== null) { - const matchStart = match.index; - const matchEnd = match.index + match[0].length; + for (const match of text.matchAll(emojiEmphasizePattern)) { + const matchStart = match.index ?? 0; + const matchEnd = matchStart + match[0].length; emojiEmphasizeMatches.push(match); @@ -154,16 +153,15 @@ const rule: TextlintRuleModule = (context, options = {}) => { if (!disableInfoPatterns) { const infoPrefixPattern = new RegExp(`\\*\\*(${infoPatterns.join("|")})([::].*?)?\\*\\*`, "g"); - let match; - while ((match = infoPrefixPattern.exec(text)) !== null) { - const matchStart = match.index; - const matchEnd = match.index + match[0].length; + for (const match of text.matchAll(infoPrefixPattern)) { + const matchStart = match.index ?? 0; + const matchEnd = matchStart + match[0].length; const prefixText = match[1]; // 絵文字+太字のマッチと重複していないかチェック const isOverlapping = emojiEmphasizeMatches.some((emojiMatch) => { - const emojiStart = emojiMatch.index!; - const emojiEnd = emojiMatch.index! + emojiMatch[0].length; + const emojiStart = emojiMatch.index ?? 0; + const emojiEnd = emojiStart + emojiMatch[0].length; return matchStart < emojiEnd && matchEnd > emojiStart; }); diff --git a/src/rules/no-ai-hype-expressions.ts b/src/rules/no-ai-hype-expressions.ts index 4620190..d709b02 100644 --- a/src/rules/no-ai-hype-expressions.ts +++ b/src/rules/no-ai-hype-expressions.ts @@ -1,7 +1,7 @@ -import type { TextlintRuleModule } from "@textlint/types"; import { matchPatterns } from "@textlint/regexp-string-matcher"; +import type { TextlintRuleContext, TextlintRuleModule } from "@textlint/types"; -export interface Options { +type Options = { // If node's text includes allowed patterns, does not report. // Can be string or RegExp-like string ("/pattern/flags") allows?: string[]; @@ -9,9 +9,9 @@ export interface Options { disableAbsolutenessPatterns?: boolean; disableAbstractPatterns?: boolean; disabledPredictivePatterns?: boolean; -} +}; -const rule: TextlintRuleModule = (context, options = {}) => { +const rule: TextlintRuleModule = (context: TextlintRuleContext, options = {}) => { const { Syntax, RuleError, report, getSource, locator } = context; const allows = options.allows ?? []; const disableAbsolutenessPatterns = options.disableAbsolutenessPatterns ?? false; @@ -176,7 +176,7 @@ const rule: TextlintRuleModule = (context, options = {}) => { for (const { pattern, message } of patterns) { const matches = text.matchAll(pattern); for (const match of matches) { - const index = match.index ?? 0; + const index: number = match.index ?? 0; const matchRange = [index, index + match[0].length] as const; const ruleError = new RuleError(message, { padding: locator.range(matchRange) diff --git a/src/rules/no-ai-list-formatting.ts b/src/rules/no-ai-list-formatting.ts index 9bed202..d827428 100644 --- a/src/rules/no-ai-list-formatting.ts +++ b/src/rules/no-ai-list-formatting.ts @@ -1,16 +1,16 @@ -import type { TextlintRuleModule } from "@textlint/types"; import { matchPatterns } from "@textlint/regexp-string-matcher"; +import type { TextlintRuleContext, TextlintRuleModule } from "@textlint/types"; -export interface Options { +type Options = { // If node's text includes allowed patterns, does not report. // Can be string or RegExp-like string ("/pattern/flags") allows?: string[]; // Disable specific pattern checks disableBoldListItems?: boolean; disableEmojiListItems?: boolean; -} +}; -const rule: TextlintRuleModule = (context, options = {}) => { +const rule: TextlintRuleModule = (context: TextlintRuleContext, options = {}) => { const { Syntax, RuleError, report, getSource, locator } = context; const allows = options.allows ?? []; const disableBoldListItems = options.disableBoldListItems ?? false; @@ -80,9 +80,9 @@ const rule: TextlintRuleModule = (context, options = {}) => { // Check for bold list item pattern: - **text**: description if (!disableBoldListItems) { const boldListPattern = /^[\s]*[-*+]\s+\*\*([^*]+)\*\*\s*:/; - const boldMatch = text.match(boldListPattern); + const boldMatch: RegExpMatchArray | null = text.match(boldListPattern); if (boldMatch) { - const matchStart = boldMatch.index ?? 0; + const matchStart: number = boldMatch.index ?? 0; const matchEnd = matchStart + boldMatch[0].length; const matchRange = [matchStart, matchEnd] as const; const ruleError = new RuleError( @@ -97,10 +97,10 @@ const rule: TextlintRuleModule = (context, options = {}) => { // Check for emoji list items if (!disableEmojiListItems) { - const emojiMatch = text.match(flashyEmojiPattern); + const emojiMatch: RegExpMatchArray | null = text.match(flashyEmojiPattern); if (emojiMatch) { const emoji = emojiMatch[0]; - const emojiIndex = emojiMatch.index ?? 0; + const emojiIndex: number = emojiMatch.index ?? 0; const matchRange = [emojiIndex, emojiIndex + emoji.length] as const; const ruleError = new RuleError( `リストアイテムでの絵文字「${emoji}」の使用は、読み手によっては機械的な印象を与える場合があります。テキストベースの表現も検討してみてください。`, diff --git a/tsconfig.json b/tsconfig.json index 809754f..420d459 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,36 +1,28 @@ { - "compilerOptions": { - /* Basic Options */ - "module": "commonjs", - "moduleResolution": "node", - "esModuleInterop": true, - "newLine": "LF", - "outDir": "./lib/", - "target": "es2015", - "sourceMap": true, - "declaration": true, - "jsx": "preserve", - "lib": [ - "esnext", - "dom" - ], - /* Strict Type-Checking Options */ - "strict": true, - /* Additional Checks */ - /* Report errors on unused locals. */ - "noUnusedLocals": true, - /* Report errors on unused parameters. */ - "noUnusedParameters": true, - /* Report error when not all code paths in function return a value. */ - "noImplicitReturns": true, - /* Report errors for fallthrough cases in switch statement. */ - "noFallthroughCasesInSwitch": true - }, - "include": [ - "src/**/*" - ], - "exclude": [ - ".git", - "node_modules" - ] -} \ No newline at end of file + "compilerOptions": { + /* Basic Options */ + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "newLine": "LF", + "outDir": "./lib/", + "target": "es2015", + "sourceMap": true, + "declaration": true, + "jsx": "preserve", + "lib": ["esnext", "dom"], + /* Strict Type-Checking Options */ + "strict": true, + /* Additional Checks */ + /* Report errors on unused locals. */ + "noUnusedLocals": true, + /* Report errors on unused parameters. */ + "noUnusedParameters": true, + /* Report error when not all code paths in function return a value. */ + "noImplicitReturns": true, + /* Report errors for fallthrough cases in switch statement. */ + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*"], + "exclude": [".git", "node_modules"] +}