diff --git a/README.md b/README.md index 124a885..e0d67a5 100644 --- a/README.md +++ b/README.md @@ -655,6 +655,42 @@ A. 正規表現の辞書ベースのルールが幾つかあります。 } ``` +Q. 半角かっこの外側のスペースを禁止したい・必須にしたい + +A. オプションで半角かっこの外側のスペースの扱いを変更することが出来ます。 + +[3.3.かっこ類と隣接する文字の間のスペースの有無](./src/3.3.js)のオプションを設定することで、半角かっこの外側のスペースの扱いを変更することができます。 + +`allowOutsideHalfParentheses` は半角かっこの外側の半角スペースを許容するオプションです。 +デフォルトは `true` です。 +`false` に設定することで、半角かっこの外側のスペースを禁止できます。 + +```json5 +{ + "rules": { + "preset-jtf-style": { + "3.3.かっこ類と隣接する文字の間のスペースの有無": { + "allowOutsideHalfParentheses": false + } + } + } +} +``` + +`requireOutsideHalfParentheses` は半角かっこの外側の半角スペースを必須にするオプションです。 +デフォルトは `false` です。 + +```json5 +{ + "rules": { + "preset-jtf-style": { + "3.3.かっこ類と隣接する文字の間のスペースの有無": { + "requireOutsideHalfParentheses": true + } + } + } +} +``` ## Migration: `textlint-plugin-jtf-style` to `textlint-rule-preset-jtf-style` diff --git a/src/3.3.js b/src/3.3.js index a48a9e3..5b02284 100644 --- a/src/3.3.js +++ b/src/3.3.js @@ -6,17 +6,27 @@ */ import { isUserWrittenNode } from "./util/node-util"; import { matchCaptureGroupAll } from "match-index"; +import { japaneseRegExp } from "./util/regexp"; -const brackets = ["\\[", "\\]", "(", ")", "[", "]", "「", "」", "『", "』"]; - +const brackets = ["\\(", "\\)", "\\[", "\\]", "(", ")", "[", "]", "「", "」", "『", "』"]; const leftBrackets = brackets.map((bracket) => { return new RegExp("([  ])" + bracket, "g"); }); const rightBrackets = brackets.map((bracket) => { return new RegExp(bracket + "([  ])", "g"); }); -function reporter(context) { +const leftHalfParentheses = new RegExp(`${japaneseRegExp.source}(\\()`, "g"); +const rightHalfParentheses = new RegExp(`(\\))${japaneseRegExp.source}`, "g"); +const defaultOptions = { + allowOutsideHalfParentheses: true, + requireOutsideHalfParentheses: false +}; +function reporter(context, options) { let { Syntax, RuleError, report, fixer, getSource } = context; + const allowOutsideHalfParentheses = + options.allowOutsideHalfParentheses ?? defaultOptions.allowOutsideHalfParentheses; + const requireOutsideHalfParentheses = + options.requireOutsideHalfParentheses ?? defaultOptions.requireOutsideHalfParentheses; return { [Syntax.Str](node) { if (!isUserWrittenNode(node, context)) { @@ -27,6 +37,9 @@ function reporter(context) { leftBrackets.forEach((pattern) => { matchCaptureGroupAll(text, pattern).forEach((match) => { const { index } = match; + if (allowOutsideHalfParentheses && text.substring(index, index + 2) === " (") { + return; + } report( node, new RuleError("かっこの外側、内側ともにスペースを入れません。", { @@ -39,7 +52,10 @@ function reporter(context) { // 右にスペース rightBrackets.forEach((pattern) => { matchCaptureGroupAll(text, pattern).forEach((match) => { - const { index, text } = match; + const { index } = match; + if (allowOutsideHalfParentheses && text.substring(index - 1, index + 1) === ") ") { + return; + } report( node, new RuleError("かっこの外側、内側ともにスペースを入れません。", { @@ -49,6 +65,30 @@ function reporter(context) { ); }); }); + if (requireOutsideHalfParentheses) { + // 左にスペース必須 + matchCaptureGroupAll(text, leftHalfParentheses).forEach((match) => { + const { index } = match; + report( + node, + new RuleError("半角かっこの外側に半角スペースが必要です。", { + index, + fix: fixer.replaceTextRange([index, index + 1], " " + match.text) + }) + ); + }); + // 右にスペース必須 + matchCaptureGroupAll(text, rightHalfParentheses).forEach((match) => { + const { index } = match; + report( + node, + new RuleError("半角かっこの外側に半角スペースが必要です。", { + index, + fix: fixer.replaceTextRange([index, index + 1], match.text + " ") + }) + ); + }); + } } }; } diff --git a/src/4.3.1.js b/src/4.3.1.js index 6c6d03c..412855d 100644 --- a/src/4.3.1.js +++ b/src/4.3.1.js @@ -31,10 +31,10 @@ function reporter(context) { // 半角のかっこ()は使用しないで全角のかっこを使用する const text = getSource(node); const matchRegExps = [ - // FIXME: https://github.com/textlint-ja/textlint-rule-preset-JTF-style/issues/79 - // rx`([\(\)])(?:${japaneseRegExp}+)([\(\)])`, - // rx`([\(\)])(?:${japaneseRegExp})`, - rx`(?:${japaneseRegExp})([\(\)])` + rx`([\(\)])(?:.*${japaneseRegExp}.*)([\(\)])`, + rx`(?:${japaneseRegExp})([\(\)])(?:${japaneseRegExp})`, + rx`^(\()(?:${japaneseRegExp})`, + rx`(?:${japaneseRegExp})(\))$` ]; matchRegExps.forEach((matchRegExp) => { matchCaptureGroupAll(text, matchRegExp).forEach((match) => { diff --git a/test/3.3-test.js b/test/3.3-test.js index 0a35231..6638335 100644 --- a/test/3.3-test.js +++ b/test/3.3-test.js @@ -7,12 +7,19 @@ tester.run("3.3.かっこ類と隣接する文字の間のスペースの有無" valid: [ "「良い」", "テスト[文章]です", + "これは (test) です", ` 実装をみてもらうと分かりますが、JavaScriptの\`prototype\`の仕組みをそのまま利用しています。 そのため、特別な実装は必要なく 「拡張する時は\`calculator.prototype\`の代わりに\`calculator.fn\`を拡張してください」 というルールがあるだけとも言えます。 -` +`, + { + text: "Page(s)", + options: { + requireOutsideHalfParentheses: true + } + } ], invalid: [ { @@ -31,6 +38,60 @@ tester.run("3.3.かっこ類と隣接する文字の間のスペースの有無" } ] }, + { + text: "これは (ダメ) です", + output: "これは(ダメ)です", + errors: [ + { + message: "かっこの外側、内側ともにスペースを入れません。", + line: 1, + column: 4 + }, + { + message: "かっこの外側、内側ともにスペースを入れません。", + line: 1, + column: 9 + } + ] + }, + { + text: "これはダメ (test) です", + output: "これはダメ(test)です", + options: { + allowOutsideHalfParentheses: false + }, + errors: [ + { + message: "かっこの外側、内側ともにスペースを入れません。", + line: 1, + column: 6 + }, + { + message: "かっこの外側、内側ともにスペースを入れません。", + line: 1, + column: 13 + } + ] + }, + { + text: "これはダメ(test)です", + output: "これはダメ (test) です", + options: { + requireOutsideHalfParentheses: true + }, + errors: [ + { + message: "半角かっこの外側に半角スペースが必要です。", + line: 1, + column: 6 + }, + { + message: "半角かっこの外側に半角スペースが必要です。", + line: 1, + column: 11 + } + ] + }, { text: `TEST diff --git a/test/4.3.1-test.js b/test/4.3.1-test.js index 56e1fef..2ab31a6 100644 --- a/test/4.3.1-test.js +++ b/test/4.3.1-test.js @@ -6,11 +6,14 @@ var tester = new TextLintTester(); tester.run("4.3.1.丸かっこ()", rule, { valid: [ "クォーク(物質の素粒子)", + "(物質の素粒子)クォーク", "(物質の素粒子)", "(npm 2.x以上をインストールしている必要があります)", + "(必要バージョン: 2.x)", // 英語のみの半角括弧は許可 // https://github.com/textlint-ja/textlint-rule-preset-JTF-style/issues/79 - "これは (English text in half-width parens) です。" + "これは (English text in half-width parens) です。", + "これは(English text in half-width parens)です。" ], invalid: [ { @@ -28,41 +31,70 @@ tester.run("4.3.1.丸かっこ()", rule, { } ] }, - // // FIXME: https://github.com/textlint-ja/textlint-rule-preset-JTF-style/issues/79 - // { - // // 半角かっこ - // text: "(物質の素粒子)", - // output: "(物質の素粒子)", - // errors: [ - // { - // message: "半角のかっこ()が使用されています。全角のかっこ()を使用してください。", - // column: 1 - // }, - // { - // message: "半角のかっこ()が使用されています。全角のかっこ()を使用してください。", - // column: 8 - // } - // ] - // }, - // { - // // 半角かっこ - // text: "(npm 2.x以上をインストールしている必要があります)", - // output: "(npm 2.x以上をインストールしている必要があります)", - // errors: [ - // { - // message: "半角のかっこ()が使用されています。全角のかっこ()を使用してください。", - // column: 1 - // }, - // { - // message: "半角のかっこ()が使用されています。全角のかっこ()を使用してください。", - // column: 29 - // } - // ] - // }, { // 半角かっこ - text: "例)test", - output: "例)test", + text: "(物質の素粒子)クォーク", + output: "(物質の素粒子)クォーク", + errors: [ + { + message: "半角のかっこ()が使用されています。全角のかっこ()を使用してください。", + column: 1 + }, + { + message: "半角のかっこ()が使用されています。全角のかっこ()を使用してください。", + column: 8 + } + ] + }, + { + // 半角かっこ + text: "(物質の素粒子)", + output: "(物質の素粒子)", + errors: [ + { + message: "半角のかっこ()が使用されています。全角のかっこ()を使用してください。", + column: 1 + }, + { + message: "半角のかっこ()が使用されています。全角のかっこ()を使用してください。", + column: 8 + } + ] + }, + { + // 半角かっこ + text: "(npm 2.x以上をインストールしている必要があります)", + output: "(npm 2.x以上をインストールしている必要があります)", + errors: [ + { + message: "半角のかっこ()が使用されています。全角のかっこ()を使用してください。", + column: 1 + }, + { + message: "半角のかっこ()が使用されています。全角のかっこ()を使用してください。", + column: 29 + } + ] + }, + { + // 半角かっこ + text: "(必要バージョン: 2.x)", + output: "(必要バージョン: 2.x)", + errors: [ + { + message: "半角のかっこ()が使用されています。全角のかっこ()を使用してください。", + column: 1 + }, + { + message: "半角のかっこ()が使用されています。全角のかっこ()を使用してください。", + column: 14 + } + ] + }, + { + // 半角かっこ + text: "例)テスト", + output: "例)テスト", errors: [ { message: "半角のかっこ()が使用されています。全角のかっこ()を使用してください。", diff --git a/test/fixtures/input.md b/test/fixtures/input.md index 841dc32..d7d49ea 100644 --- a/test/fixtures/input.md +++ b/test/fixtures/input.md @@ -73,6 +73,8 @@ JTF 標準 これは 「ダメ」です +これは (ダメ) です + A氏は「5月に新製品を発売します。」と述べました。 従業員は約30,000人です(関連企業を含みます。) @@ -91,7 +93,15 @@ A氏は「5月に新製品を発売します。」と述べました。 クォーク(物質の素粒子) -例)test +(物質の素粒子)クォーク + +(物質の素粒子) + +(npm 2.x以上をインストールしている必要があります) + +(必要バージョン: 2.x) + +例)テスト 半角[かっこ diff --git a/test/fixtures/output.md b/test/fixtures/output.md index cce39f2..a2f7afd 100644 --- a/test/fixtures/output.md +++ b/test/fixtures/output.md @@ -73,6 +73,8 @@ JTF標準 これは「ダメ」です +これは(ダメ)です + A氏は「5月に新製品を発売します」と述べました。 従業員は約30,000人です(関連企業を含みます) @@ -91,7 +93,15 @@ A氏は「5月に新製品を発売します」と述べました。 クォーク(物質の素粒子) -例)test +(物質の素粒子)クォーク + +(物質の素粒子) + +(npm 2.x以上をインストールしている必要があります) + +(必要バージョン: 2.x) + +例)テスト 半角[かっこ diff --git a/tool/create-fixtures.js b/tool/create-fixtures.js index db248a2..144d4bb 100644 --- a/tool/create-fixtures.js +++ b/tool/create-fixtures.js @@ -13,13 +13,15 @@ function processFile(filePath) { const lines = contents.split(/\n/); const inputRegExp = /^\s+text:\s*?"(.*?)"/; const outputRegExp = /^\s+output:\s*?"(.*?)"/; + const optionsRegExp = /^\s+options:\s*?\{/; lines.forEach(function (line, index) { const nextLine = lines[index + 1]; - if (inputRegExp.test(line) && outputRegExp.test(nextLine)) { + const nextNextLine = lines[index + 2]; + if (inputRegExp.test(line) && outputRegExp.test(nextLine) && !optionsRegExp.test(nextNextLine)) { const inputMatch = line.match(inputRegExp)[1]; // \\n => \n RESULT.input.push(inputMatch.replace(/\\n/g, "\n")); - } else if (outputRegExp.test(line)) { + } else if (outputRegExp.test(line) && !optionsRegExp.test(nextLine)) { const outputMatch = line.match(outputRegExp)[1]; RESULT.output.push(outputMatch.replace(/\\n/g, "\n")); }