diff --git a/README.md b/README.md index 10f986c..41616e2 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,54 @@ AIライティングで過度に使用されがちな誇張表現やハイプ的 AIが機械的に生成しがちな強調パターンを検出します。 +### no-ai-colon-continuation + +コロンの直後にブロック要素が続く英語的なパターンを検出します。日本語として自然な表現を促進するルールです。 + +このルールは形態素解析(kuromojin)を使用して、コロンの前の文が述語(動詞・形容詞・助動詞)で終わっているかを判定します。「使用方法:」のような名詞で終わる表現は自然な日本語として許可され、「実行します:」のような述語で終わる表現のみを検出します。 + +#### 検出される例 + +````markdown +実行します: + +```bash +command +``` + +説明します: + +- 項目1 +- 項目2 + +例えば: + +- 具体的な例 +```` + +#### より自然な日本語表現 + +````markdown +実行方法は以下の通りです。 + +```bash +command +``` + +説明の内容は以下の通りです。 + +- 項目1 +- 項目2 + +たとえば、次のような例があります。 + +- 具体的な例 + +例: + +- 具体的な例 +```` + ### ai-tech-writing-guideline テクニカルライティングのベストプラクティスに基づいて、文書品質の改善提案を行います。 @@ -279,6 +327,13 @@ AIを利用しやすくするプラットフォームです。 "disableAbstractPatterns": false, "disabledPredictivePatterns": false }, + "no-ai-colon-continuation": { + "allows": ["許可したいテキスト", "/正規表現パターン/"], + "disableCodeBlock": false, + "disableList": false, + "disableQuote": false, + "disableTable": false + }, "ai-tech-writing-guideline": { "severity": "info", // サジェストとして扱う "allows": ["許可したいテキスト", "/正規表現パターン/"], @@ -304,6 +359,16 @@ AIを利用しやすくするプラットフォームです。 - `disableBoldListItems`: `true`にすると強調リストアイテムの検出を無効にする - `disableEmojiListItems`: `true`にすると絵文字リストアイテムの検出を無効にする +#### no-ai-colon-continuation + +- `allows`: 指定したパターンにマッチする場合、エラーを報告しません + - 文字列: `"許可したいテキスト"` + - 正規表現: `"/パターン/フラグ"` (例: `"/使用方法.*/i"`) +- `disableCodeBlock`: `true`にするとコロン後のコードブロック検出を無効にする +- `disableList`: `true`にするとコロン後のリスト検出を無効にする +- `disableQuote`: `true`にするとコロン後の引用検出を無効にする +- `disableTable`: `true`にするとコロン後のテーブル検出を無効にする + ### 正規表現パターンの使用例 `allows`オプションでは、[regexp-string-matcher](https://github.com/textlint/regexp-string-matcher)の形式で正規表現パターンを指定できます。 diff --git a/package-lock.json b/package-lock.json index 6284022..c6c5c72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@textlint/regexp-string-matcher": "^2.0.2", + "kuromojin": "^3.0.1", "textlint-util-to-string": "^3.3.4" }, "devDependencies": { @@ -1654,7 +1655,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@kvs/env/-/env-2.2.0.tgz", "integrity": "sha512-G66d4q8ATY7jsKz6KovHE9ScQ4HSs9BtkRUiLMVI/nHeHHA+KDt4qYZQ2KmaC9LPIAc7bJmR7uAH8DTBamJKhw==", - "dev": true, "license": "MIT", "dependencies": { "@kvs/indexeddb": "^2.1.4", @@ -1665,7 +1665,6 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@kvs/indexeddb/-/indexeddb-2.1.4.tgz", "integrity": "sha512-7xIF5hLREZFXnsYnR51LTAaO+pWFDMoBzF2vdl/RVnPP+7/WJS+7npO1aZ+EDrAMeJwAARhXFF/3NI3Wy3DCXQ==", - "dev": true, "license": "MIT", "dependencies": { "@kvs/types": "^2.1.4" @@ -1675,7 +1674,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@kvs/node-localstorage/-/node-localstorage-2.2.0.tgz", "integrity": "sha512-ADvFxbCZF2t4vzwU2Ng3I7+vviSLTc5bkCGgVUeEfcRqaM781ZAO5bHn9kiYUIziIf5fESkOnQxrJQqmQtpI0g==", - "dev": true, "license": "MIT", "dependencies": { "@kvs/storage": "^2.1.4", @@ -1687,7 +1685,6 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@kvs/storage/-/storage-2.1.4.tgz", "integrity": "sha512-d4sdTdCjAsyX9v9Q3rFciG9rUs24KNIMUzRv3fVIpbehlnfTWZ5TACucN6LAMFZaqgEnBKLxghwfUCrsaYDzig==", - "dev": true, "license": "MIT", "dependencies": { "@kvs/types": "^2.1.4" @@ -1697,7 +1694,6 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@kvs/types/-/types-2.1.4.tgz", "integrity": "sha512-XtTOUatnJrDcuYNPfN+9x1xuVp11IuyPi9zOLVJPpfnKp+vUCatka/Crp2cfz0uKt4QE1Pb4Dg/TNYgVYA2Org==", - "dev": true, "license": "MIT" }, "node_modules/@modelcontextprotocol/sdk": { @@ -2143,7 +2139,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6.0.0" @@ -2945,7 +2940,6 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/doublearray/-/doublearray-0.0.2.tgz", "integrity": "sha512-aw55FtZzT6AmiamEj2kvmR6BuFqvYgKZUkfQ7teqVRNqD5UE0rw8IeW/3gieHNKQ5sPuDKlljWEn4bzv5+1bHw==", - "dev": true, "license": "MIT" }, "node_modules/dunder-proto": { @@ -3620,7 +3614,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/has-flag": { @@ -3807,7 +3800,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -4159,7 +4151,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/kuromoji/-/kuromoji-0.1.2.tgz", "integrity": "sha512-V0dUf+C2LpcPEXhoHLMAop/bOht16Dyr+mDiIE39yX3vqau7p80De/koFqpiTcL1zzdZlc3xuHZ8u5gjYRfFaQ==", - "dev": true, "license": "Apache-2.0", "dependencies": { "async": "^2.0.1", @@ -4171,7 +4162,6 @@ "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, "license": "MIT", "dependencies": { "lodash": "^4.17.14" @@ -4181,7 +4171,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/kuromojin/-/kuromojin-3.0.1.tgz", "integrity": "sha512-E4rLmbTid8ZGH8Fw421UXvfgCe0IXGFKhUw/IosBVW6ootxVGKGbtb0UPQAvPswgsXX/8UuPiE+amz1NN11Uzg==", - "dev": true, "license": "MIT", "dependencies": { "@kvs/env": "^2.1.3", @@ -4428,7 +4417,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, "license": "MIT" }, "node_modules/lodash.debounce": { @@ -4627,7 +4615,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", "integrity": "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==", - "dev": true, "license": "MIT" }, "node_modules/lru-cache": { @@ -5207,7 +5194,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/node-localstorage/-/node-localstorage-2.2.1.tgz", "integrity": "sha512-vv8fJuOUCCvSPjDjBLlMqYMHob4aGjkmrkaE42/mZr0VT+ZAU10jRF8oTnX9+pgU9/vYJ8P7YT3Vd6ajkmzSCw==", - "dev": true, "license": "MIT", "dependencies": { "write-file-atomic": "^1.1.4" @@ -6540,7 +6526,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", - "dev": true, "license": "ISC", "engines": { "node": "*" @@ -7675,7 +7660,6 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", "integrity": "sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==", - "dev": true, "license": "ISC", "dependencies": { "graceful-fs": "^4.1.11", @@ -7780,7 +7764,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==", - "dev": true, "license": "MIT", "engines": { "node": "*" diff --git a/package.json b/package.json index 1607557..4d362d3 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ }, "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 0b1769d..407ffe6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,13 +2,15 @@ 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"; const preset = { rules: { "no-ai-list-formatting": noAiListFormatting, "no-ai-hype-expressions": noAiHypeExpressions, "no-ai-emphasis-patterns": noAiEmphasisPatterns, - "ai-tech-writing-guideline": aiTechWritingGuideline + "ai-tech-writing-guideline": aiTechWritingGuideline, + "no-ai-colon-continuation": noAiColonContinuation }, rulesConfig: { "no-ai-list-formatting": true, @@ -16,7 +18,8 @@ const preset = { "no-ai-emphasis-patterns": true, "ai-tech-writing-guideline": { severity: "info" - } + }, + "no-ai-colon-continuation": true } }; diff --git a/src/rules/ai-tech-writing-guideline.ts b/src/rules/ai-tech-writing-guideline.ts index 428f623..aeda9b5 100644 --- a/src/rules/ai-tech-writing-guideline.ts +++ b/src/rules/ai-tech-writing-guideline.ts @@ -188,6 +188,7 @@ const rule: TextlintRuleModule = (context, options = {}) => { /** * 機械的な段落と箇条書きの組み合わせパターンを検出 + * 注意: コロン関連のパターンは no-ai-colon-continuation ルールで処理されます */ const detectMechanicalListIntroPattern = (node: any) => { const children = node.children || []; @@ -213,14 +214,10 @@ const rule: TextlintRuleModule = (context, options = {}) => { let isDetected = false; let message = ""; - // パターン1: コロン(:、:)で終わる段落 - if (/[::][\s]*$/.test(paragraphText.trim())) { - isDetected = true; - message = - "【構造化】コロン(:)で終わる文の直後の箇条書きは機械的な印象を与える可能性があります。「たとえば、次のような点があります。」のような導入文を使った自然な表現を検討してください。"; - } - // パターン2: 「例えば。」「具体的には。」など、接続表現+句点で終わる段落 - else if (/(?:例えば|具体的には|詳細には|以下|次に|また)。[\s]*$/.test(paragraphText.trim())) { + // 注意: コロンパターンは no-ai-colon-continuation で処理されるため削除 + + // パターン: 「例えば。」「具体的には。」など、接続表現+句点で終わる段落 + if (/(?:例えば|具体的には|詳細には|以下|次に|また)。[\s]*$/.test(paragraphText.trim())) { isDetected = true; message = "【構造化】接続表現と句点で終わる文の直後の箇条書きは機械的な印象を与える可能性があります。「たとえば、次のような点があります。」のような自然な導入文を検討してください。"; @@ -254,7 +251,8 @@ const rule: TextlintRuleModule = (context, options = {}) => { return; } - // コロン + 箇条書きパターンの検出 + // 接続表現 + 箇条書きパターンの検出 + // 注意: コロン + ブロック要素パターンは no-ai-colon-continuation ルールで処理 detectMechanicalListIntroPattern(node); // 将来的にここに他の文書レベルの構造化パターンを追加できます // 例: diff --git a/src/rules/no-ai-colon-continuation.ts b/src/rules/no-ai-colon-continuation.ts new file mode 100644 index 0000000..b2d647a --- /dev/null +++ b/src/rules/no-ai-colon-continuation.ts @@ -0,0 +1,163 @@ +import { matchPatterns } from "@textlint/regexp-string-matcher"; +import { StringSource } from "textlint-util-to-string"; +import { tokenize } from "kuromojin"; + +/** + * コロンの直後にブロック要素が続くパターンを検出するルール + * + * 目的: + * AI生成文章でよく見られる英語構文の直訳パターンを検出します。 + * ただし、「使用方法:」のような名詞で終わる表現は自然な日本語として許可されます。 + * 問題となるのは「実行します:」のような述語で終わる表現です。 + * + * より自然な日本語表現: + * - 「使用方法は以下の通りです」 + * - 「次のように使用します」 + * - 「以下の手順で実行してください」 + */ +const rule = (context: any, options: any = {}) => { + const { Syntax, RuleError, report, getSource, locator } = context; + const allows = options.allows ?? []; + const disableCodeBlock = options.disableCodeBlock ?? false; + const disableList = options.disableList ?? false; + const disableQuote = options.disableQuote ?? false; + const disableTable = options.disableTable ?? false; + + // AST走査で隣接するノードの組み合わせをチェック + + const checkColonContinuation = async (paragraphNode: any, nextNode: any) => { + // Paragraphノードのテキストを取得 + const paragraphText = getSource(paragraphNode); + + // Check if text matches any allowed patterns + if (allows.length > 0) { + const matches = matchPatterns(paragraphText, allows); + if (matches.length > 0) { + return; + } + } + + // コロン(半角・全角)で終わっているかチェック + if (!/[::]$/.test(paragraphText.trim())) { + return; + } + + // StringSourceを使ってMarkdownを取り除いたテキストを取得 + const stringSource = new StringSource(paragraphNode); + const plainText = stringSource.toString().trim(); + + // プレーンテキストでもコロン(半角・全角)で終わっているかチェック + if (!/[::]$/.test(plainText)) { + return; + } + + // コロンの種類を特定 + const isFullWidthColon = plainText.endsWith(":"); + const colonChar = isFullWidthColon ? ":" : ":"; + + // コロンを除いたテキストを取得 + const beforeColonText = plainText.slice(0, -1); + + // kuromojinで形態素解析を行い、名詞で終わっているかを判定 + const isNoun = await (async () => { + // 英語のテキストかどうかを判定(英語の場合はコロンが自然なので許可) + const isEnglishText = + /^[a-zA-Z0-9\s\-_.]+$/.test(beforeColonText.trim()) && /[a-zA-Z]/.test(beforeColonText); + if (isEnglishText) { + return true; // 英語テキストの場合は許可 + } + + // 短すぎる場合(1-2文字)は名詞として扱う + if (beforeColonText.length <= 2) { + return true; + } + + try { + // kuromojinで形態素解析 + const tokens = await tokenize(beforeColonText); + + if (tokens.length === 0) { + return true; // 解析できない場合は許可 + } + + // 最後のトークンの品詞をチェック + const lastToken = tokens[tokens.length - 1]; + const partOfSpeech = lastToken.pos.split(",")[0]; // 大分類を取得 + + // 名詞で終わっている場合は許可 + if (partOfSpeech === "名詞") { + return true; + } + + // 動詞、形容詞、助動詞で終わっている場合は述語として判定 + if (["動詞", "形容詞", "助動詞"].includes(partOfSpeech)) { + return false; + } + + // 接続詞の場合もエラーとする(「例えば:」等は機械的パターン) + if (partOfSpeech === "接続詞") { + return false; + } + + // その他の品詞(助詞等)の場合は文脈による + // より保守的にエラーとする + return false; + } catch (error) { + // 形態素解析でエラーが発生した場合は許可(保守的にエラーを避ける) + return true; + } + })(); + + if (isNoun) { + return; // 名詞の場合はエラーにしない + } + + // 次のノードの種類をチェック + const shouldReport = (() => { + if (!nextNode) return false; + + switch (nextNode.type) { + case Syntax.CodeBlock: + return !disableCodeBlock; + case Syntax.List: + return !disableList; + case Syntax.BlockQuote: + return !disableQuote; + case Syntax.Table: + return !disableTable; + default: + return false; + } + })(); + + if (shouldReport) { + // paragraphTextでのコロン位置を計算(StringSourceとの位置差を考慮) + const paragraphColonIndex = paragraphText.lastIndexOf(colonChar); + const matchRange = [paragraphColonIndex, paragraphColonIndex + 1] as const; + + const ruleError = new RuleError( + `「${beforeColonText}${colonChar}」のようなパターンは、読み手によっては英語の構文を直訳したような印象を与える場合があります。より自然な日本語表現を検討してください。`, + { + padding: locator.range(matchRange) + } + ); + report(paragraphNode, ruleError); + } + }; + + return { + async [Syntax.Document](node: any) { + // ドキュメントの子ノードを順番にチェック + for (const [i, currentNode] of node.children.entries()) { + const nextNode = node.children[i + 1]; + + // Paragraphノードの後にブロック要素が続く場合をチェック + if (currentNode.type === Syntax.Paragraph) { + await checkColonContinuation(currentNode, nextNode); + } + } + } + }; +}; + +export default rule; diff --git a/test/rules/ai-tech-writing-guideline.test.ts b/test/rules/ai-tech-writing-guideline.test.ts index b771a6d..d75df87 100644 --- a/test/rules/ai-tech-writing-guideline.test.ts +++ b/test/rules/ai-tech-writing-guideline.test.ts @@ -127,27 +127,7 @@ tester.run("ai-tech-writing-guideline", rule, { } ] }, - // 構造化の問題(コロンと箇条書きの組み合わせ) - { - text: "Vueのリアクティビティシステムは確かに便利ですが、その仕組みの見えにくさが気になります。例えば:\n\n- refとreactiveの使い分けが最初は分からない。", - options: { enableDocumentAnalysis: false }, - errors: [ - { - message: - "【構造化】コロン(:)で終わる文の直後の箇条書きは機械的な印象を与える可能性があります。「たとえば、次のような点があります。」のような導入文を使った自然な表現を検討してください。" - } - ] - }, - { - text: "JSXはJavaScriptの中でUIを記述するため、プログラマーにとって理解しやすいです:\n\n- 条件分岐やループは通常のJavaScriptの記法", - options: { enableDocumentAnalysis: false }, - errors: [ - { - message: - "【構造化】コロン(:)で終わる文の直後の箇条書きは機械的な印象を与える可能性があります。「たとえば、次のような点があります。」のような導入文を使った自然な表現を検討してください。" - } - ] - }, + // 構造化の問題(コロン関連パターンは no-ai-colon-continuation ルールに移行) // 構造化の問題(接続表現と句点の組み合わせ) { text: "モダンフレームワークには多くの利点があります。例えば。\n\n- パフォーマンスの最適化\n- 開発効率の向上", diff --git a/test/rules/no-ai-colon-continuation.test.ts b/test/rules/no-ai-colon-continuation.test.ts new file mode 100644 index 0000000..81426a1 --- /dev/null +++ b/test/rules/no-ai-colon-continuation.test.ts @@ -0,0 +1,141 @@ +import TextLintTester from "textlint-tester"; +import noAiColonContinuation from "../../src/rules/no-ai-colon-continuation"; + +const tester = new TextLintTester(); + +tester.run("no-ai-colon-continuation", noAiColonContinuation, { + valid: [ + // Normal text without colon-block patterns + "これは通常のテキストです。", + "コードの例:\nfunction test() { return true; }", + + // Valid noun + colon patterns (should NOT be detected) + "使用方法:\n```bash\ncommand\n```", + "API仕様:\n```javascript\napi.call()\n```", + "手順:\n- ステップ1\n- ステップ2", + "設定:\n> 重要な設定です", + "データ:\n| 列1 | 列2 |\n|-----|-----|", + + // English text should be allowed + "Install with npm:\n```bash\nnpm install\n```", + "Install with [npm](https://www.npmjs.com/package/example):\n```bash\nnpm install\n```", + "Run the test:\n```bash\nnpm test\n```", + + // Passive forms should be allowed (noun-like) + "検出される例:\n```javascript\ncode\n```", + "表示される結果:\n- 結果1\n- 結果2", + "実行される手順:\n> 手順の説明", + + // Attributive forms + noun should be allowed (noun-like) + "検出する例:\n```javascript\ncode\n```", + "実行する方法:\n- 方法1\n- 方法2", + "設定する手順:\n> 手順の説明", + + // Simple nouns should be allowed + "使い方:\n```bash\ncommand\n```", + "例:\n- 項目1\n- 項目2", + "正解:\n> これが正解です", + + // Allowed patterns (string) + { + text: "これは使用します:\n```bash\ncommand\n```", + options: { + allows: ["使用します"] + } + }, + // Allowed patterns (RegExp-like string) + { + text: "実行します:\n```javascript\nrun()\n```", + options: { + allows: ["/実行.*/"] + } + }, + // Disabled code block check + { + text: "実行します:\n```bash\ncommand\n```", + options: { + disableCodeBlock: true + } + }, + // Disabled list check + { + text: "設定します:\n- ステップ1\n- ステップ2", + options: { + disableList: true + } + }, + // Disabled quote check + { + text: "説明します:\n> これは説明です", + options: { + disableQuote: true + } + }, + // Disabled table check + { + text: "表示します:\n| 列1 | 列2 |\n|-----|-----|", + options: { + disableTable: true + } + } + ], + invalid: [ + // Predicate + colon + block patterns that should be detected + { + text: "実行します:\n```bash\ncommand\n```", + errors: [ + { + message: + "「実行します:」のようなパターンは、読み手によっては英語の構文を直訳したような印象を与える場合があります。より自然な日本語表現を検討してください。" + } + ] + }, + { + text: "説明します:\n- 項目1\n- 項目2", + errors: [ + { + message: + "「説明します:」のようなパターンは、読み手によっては英語の構文を直訳したような印象を与える場合があります。より自然な日本語表現を検討してください。" + } + ] + }, + { + text: "使用します:\n> これは使用方法です", + errors: [ + { + message: + "「使用します:」のようなパターンは、読み手によっては英語の構文を直訳したような印象を与える場合があります。より自然な日本語表現を検討してください。" + } + ] + }, + { + text: "表示します:\n| 列1 | 列2 |\n|-----|-----|", + errors: [ + { + message: + "「表示します:」のようなパターンは、読み手によっては英語の構文を直訳したような印象を与える場合があります。より自然な日本語表現を検討してください。" + } + ] + }, + + // 元々 ai-tech-writing-guideline にあったテストケースの移植 + { + text: "例えば:\n\n- refとreactiveの使い分けが最初は分からない。", + errors: [ + { + message: + "「例えば:」のようなパターンは、読み手によっては英語の構文を直訳したような印象を与える場合があります。より自然な日本語表現を検討してください。" + } + ] + }, + { + text: "JSXはJavaScriptの中でUIを記述するため、プログラマーにとって理解しやすいです:\n\n- 条件分岐やループは通常のJavaScriptの記法", + errors: [ + { + message: + "「JSXはJavaScriptの中でUIを記述するため、プログラマーにとって理解しやすいです:」のようなパターンは、読み手によっては英語の構文を直訳したような印象を与える場合があります。より自然な日本語表現を検討してください。" + } + ] + } + ] +});