Skip to content

Commit 4eec7ed

Browse files
authored
feat(space-between-half-and-full-width): add an option to ignore numbers and apply only to alphabets (#45)
## 変更点 - `space`オプションにスペースを入れる対象の配列を指定できるように - `"space": ["alphabets", "numbers", "punctuation"]` でアルファベット、数値、句読点の前後にスペースを入れる(`"space": "always"`と同じ意味) - `"space": ["alphabets", "punctuation"]` とすると アルファベットと句読点の前後にスペースを入れる - `"space": []` で全てにスペースを入れない(`"space": "never"`と同じ意味) - `exceptPunctuation` オプションは `"space": ["alphabets", "numbers"]`で代用できるため、非推奨となりました
1 parent 4fb91b2 commit 4eec7ed

File tree

3 files changed

+231
-34
lines changed

3 files changed

+231
-34
lines changed

packages/textlint-rule-ja-space-between-half-and-full-width/README.md

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
OK: これはUnicode
1010
NG: これは Unicode
1111

12-
全角文字には、句読点(、。)も含まれていますがデフォルトでは、`exceptPunctuation: true`であるため無視されます
12+
全角文字には、句読点(、。)も含まれていますがデフォルトでは、有効であるため無視されます
1313

1414
OK: これも、Unicode。
1515

@@ -42,16 +42,21 @@ textlint --rule ja-space-between-half-and-full-width README.md
4242

4343
## Options
4444

45-
- `space`: `"always"` || `"never"`
45+
- `space`: `"always"` || `"never"` || `string[]`
4646
- デフォルト: `"never"`
4747
- スペースを常に 入れる(`"always"`) or 入れない(`"never"`)
48-
- `exceptPunctuation`: `boolean`
48+
- Array 形式での指定も可能: `["alphabets", "numbers", "punctuation"]`
49+
- 対象としたい物のみ指定する
50+
- 例えば、数値と句読点(、。)を例外として扱いたい場合は以下
51+
- `["alphabets"]`
52+
- (非推奨)`exceptPunctuation`: `boolean`
4953
- デフォルト: `true`
5054
- 句読点(、。)を例外として扱うかどうか
55+
- 代わりに `space` オプションを用いて `["alphabets", "numbers"]` と指定する
5156
- `lintStyledNode`: `boolean`
5257
- デフォルト: `false`
5358
- プレーンテキスト以外(リンクや画像のキャプションなど)を lint の対象とするかどうか (プレーンテキストの判断基準は [textlint/textlint-rule-helper: This is helper library for creating textlint rule](https://github.com/textlint/textlint-rule-helper#rulehelperisplainstrnodenode-boolean) を参照してください)
54-
59+
5560
```json
5661
{
5762
"rules": {
@@ -60,26 +65,24 @@ textlint --rule ja-space-between-half-and-full-width README.md
6065
}
6166
}
6267
}
63-
```
68+
```
6469

65-
`exceptPunctuation: true`とした場合は、句読点に関しては無視されるようになります。
70+
`space` オプションに `"punctuation"` を含めない場合は、句読点に関しては無視されるようになります。
6671

67-
スペースは必須だが、`日本語、[alphabet]。`は許可する
72+
スペースは必須だが、`日本語、[alphabet]。`は許可する
6873

6974
text: "これは、Exception。",
7075
options: {
71-
space: "always",
72-
exceptPunctuation: true
76+
space: ["alphabets", "numbers"]
7377
}
7478

7579
スペースは不要だが、`日本語、 [alphabet] 。`は許可する。
7680

7781
text: "これは、 Exception 。",
7882
options: {
79-
space: "never",
80-
exceptPunctuation: true
83+
space: []
8184
}
82-
85+
8386

8487
## Changelog
8588

packages/textlint-rule-ja-space-between-half-and-full-width/src/index.js

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,58 @@ const assert = require("assert");
77
import {RuleHelper} from "textlint-rule-helper";
88
import {matchCaptureGroupAll} from "match-index";
99
const PunctuationRegExp = /[]/;
10+
const ZenRegExpStr = '[、。]|[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[ぁ-んァ-ヶ]';
11+
const defaultSpaceOptions = {
12+
alphabets: false,
13+
numbers: false,
14+
punctuation: false
15+
};
1016
const defaultOptions = {
11-
// スペースを入れるかどうか
12-
// "never" or "always"
13-
space: "never",
14-
// [。、,.]を例外とするかどうか
15-
exceptPunctuation: true,
1617
// プレーンテキスト以外を対象とするか See https://github.com/textlint/textlint-rule-helper#rulehelperisplainstrnodenode-boolean
1718
lintStyledNode: false,
1819
};
1920
function reporter(context, options = {}) {
21+
/**
22+
* 入力された `space` オプションを内部処理用に成形する
23+
* @param {string|Array|undefined} opt `space` オプションのインプット
24+
* @param {boolean|undefined} exceptPunctuation `exceptPunctuation` オプションのインプット
25+
* @returns {Object}
26+
*/
27+
const parseSpaceOption = (opt, exceptPunctuation) => {
28+
if (typeof opt === 'string') {
29+
assert(opt === "always" || opt === "never", `"space" options should be "always", "never" or an array.`);
30+
31+
if (opt === "always") {
32+
if (exceptPunctuation === false) {
33+
return {...defaultSpaceOptions, alphabets: true, numbers: true, punctuation: true};
34+
} else {
35+
return {...defaultSpaceOptions, alphabets: true, numbers: true};
36+
}
37+
} else if (opt === "never") {
38+
if (exceptPunctuation === false) {
39+
return {...defaultSpaceOptions, punctuation: true};
40+
} else {
41+
return defaultSpaceOptions;
42+
}
43+
}
44+
} else if (Array.isArray(opt)) {
45+
assert(
46+
opt.every((v) => Object.keys(defaultSpaceOptions).includes(v)),
47+
`Only "alphabets", "numbers", "punctuation" can be included in the array.`
48+
);
49+
const userOptions = Object.fromEntries(opt.map(key => [key, true]));
50+
return {...defaultSpaceOptions, ...userOptions};
51+
}
52+
53+
return defaultSpaceOptions;
54+
}
55+
2056
const {Syntax, RuleError, report, fixer, getSource} = context;
2157
const helper = new RuleHelper();
22-
const spaceOption = options.space || defaultOptions.space;
23-
const exceptPunctuation = options.exceptPunctuation !== undefined
24-
? options.exceptPunctuation
25-
: defaultOptions.exceptPunctuation;
58+
const spaceOption = parseSpaceOption(options.space, options.exceptPunctuation);
2659
const lintStyledNode = options.lintStyledNode !== undefined
2760
? options.lintStyledNode
2861
: defaultOptions.lintStyledNode;
29-
assert(spaceOption === "always" || spaceOption === "never", `"space" options should be "always" or "never".`);
3062
/**
3163
* `text`を対象に例外オプションを取り除くfilter関数を返す
3264
* @param {string} text テスト対象のテキスト全体
@@ -35,7 +67,7 @@ function reporter(context, options = {}) {
3567
*/
3668
const createFilter = (text, padding) => {
3769
/**
38-
* `exceptPunctuation`で指定された例外を取り除く
70+
* `PunctuationRegExp`で指定された例外を取り除く
3971
* @param {Object} match
4072
* @returns {boolean}
4173
*/
@@ -44,16 +76,16 @@ function reporter(context, options = {}) {
4476
if (!targetChar) {
4577
return false;
4678
}
47-
if (exceptPunctuation && PunctuationRegExp.test(targetChar)) {
79+
if (!spaceOption.punctuation && PunctuationRegExp.test(targetChar)) {
4880
return false;
4981
}
5082
return true;
5183
}
5284
};
5385
// Never: アルファベットと全角の間はスペースを入れない
5486
const noSpaceBetween = (node, text) => {
55-
const betweenHanAndZen = matchCaptureGroupAll(text, /[A-Za-z0-9]([  ])(?:[]|[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[--])/);
56-
const betweenZenAndHan = matchCaptureGroupAll(text, /(?:[]|[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[--])([  ])[A-Za-z0-9]/);
87+
const betweenHanAndZen = matchCaptureGroupAll(text, new RegExp(`[A-Za-z0-9]([  ])(?:${ZenRegExpStr})`));
88+
const betweenZenAndHan = matchCaptureGroupAll(text, new RegExp(`(?:${ZenRegExpStr})([  ])[A-Za-z0-9]`));
5789
const reportMatch = (match) => {
5890
const {index} = match;
5991
report(node, new RuleError("原則として、全角文字と半角文字の間にスペースを入れません。", {
@@ -66,12 +98,36 @@ function reporter(context, options = {}) {
6698
};
6799

68100
// Always: アルファベットと全角の間はスペースを入れる
69-
const needSpaceBetween = (node, text) => {
70-
const betweenHanAndZen = matchCaptureGroupAll(text, /([A-Za-z0-9])(?:[]|[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[--])/);
71-
const betweenZenAndHan = matchCaptureGroupAll(text, /([]|[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[--])[A-Za-z0-9]/);
101+
const needSpaceBetween = (node, text, options) => {
102+
/**
103+
* オプションを元に正規表現オプジェクトを生成する
104+
* @param {Array} opt `space` オプション
105+
* @param {boolean} btwHanAndZen=true 半角全角の間か全角半角の間か
106+
* @returns {Object}
107+
*/
108+
const generateRegExp = (opt, btwHanAndZen = true) => {
109+
const alphabets = opt.alphabets ? 'A-Za-z' : '';
110+
const numbers = opt.numbers ? '0-9' : '';
111+
112+
let expStr;
113+
if (btwHanAndZen) {
114+
expStr = `([${alphabets}${numbers}])(?:${ZenRegExpStr})`;
115+
} else {
116+
expStr = `(${ZenRegExpStr})[${alphabets}${numbers}]`;
117+
}
118+
119+
return new RegExp(expStr);
120+
};
121+
122+
const betweenHanAndZenRegExp = generateRegExp(options);
123+
const betweenZenAndHanRegExp = generateRegExp(options, false);
124+
const errorMsg = '原則として、全角文字と半角文字の間にスペースを入れます。';
125+
126+
const betweenHanAndZen = matchCaptureGroupAll(text, betweenHanAndZenRegExp);
127+
const betweenZenAndHan = matchCaptureGroupAll(text, betweenZenAndHanRegExp);
72128
const reportMatch = (match) => {
73129
const {index} = match;
74-
report(node, new RuleError("原則として、全角文字と半角文字の間にスペースを入れます。", {
130+
report(node, new RuleError(errorMsg, {
75131
index: match.index,
76132
fix: fixer.replaceTextRange([index + 1, index + 1], " ")
77133
}));
@@ -86,12 +142,12 @@ function reporter(context, options = {}) {
86142
}
87143
const text = getSource(node);
88144

89-
if (spaceOption === "always") {
90-
needSpaceBetween(node, text)
91-
} else if (spaceOption === "never") {
145+
const noSpace = (key) => key === 'punctuation' ? true : !spaceOption[key];
146+
if (Object.keys(spaceOption).every(noSpace)) {
92147
noSpaceBetween(node, text);
148+
} else {
149+
needSpaceBetween(node, text, spaceOption);
93150
}
94-
95151
}
96152
}
97153
}

0 commit comments

Comments
 (0)