Skip to content

Commit c80735b

Browse files
authored
feat(textlint-rule-spacing): add exceptPunctuation option (#12)
句読点(、。)を例外として扱うかどうかのオプションを追加 "句読点と英単語の間はスペース入れないルール"もこれで満たせる。 (always && exceptPunctuation: true) デフォルトの挙動を変更 (never && exceptPunctuation: true) close #11
1 parent 632e39f commit c80735b

File tree

4 files changed

+176
-16
lines changed

4 files changed

+176
-16
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"babel-preset-es2015": "^6.9.0",
44
"babel-register": "^6.9.0",
55
"lerna": "2.0.0-beta.24",
6-
"mocha": "^2.5.3"
6+
"mocha": "^3.1.0"
77
},
88
"scripts": {
99
"bootstrap": "lerna bootstrap",

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
OK: これはUnicode
1010
NG: これは Unicode
1111

12+
全角文字には、句読点(、。)も含まれていますがデフォルトでは、`exceptPunctuation: true`であるため無視されます。
13+
14+
OK: これも、Unicode。
15+
1216
## Install
1317

1418
Install with [npm](https://www.npmjs.com/):
@@ -41,6 +45,9 @@ textlint --rule ja-space-between-half-and-full-width README.md
4145
- `space`: `"always"` || `"never"`
4246
- デフォルト: `"never"`
4347
- スペースを常に 入れる(`"always"`) or 入れない(`"never"`)
48+
- `exceptPunctuation`: `boolean`
49+
- デフォルト: `true`
50+
- 句読点(、。)を例外として扱うかどうか
4451

4552
```json
4653
{
@@ -52,6 +59,25 @@ textlint --rule ja-space-between-half-and-full-width README.md
5259
}
5360
```
5461

62+
`exceptPunctuation: true`とした場合は、句読点に関しては無視されるようになります。
63+
64+
スペースは必須だが、`日本語、[alphabet]。`は許可する
65+
66+
text: "これは、Exception。",
67+
options: {
68+
space: "always",
69+
exceptPunctuation: true
70+
}
71+
72+
スペースは不要だが、`日本語、 [alphabet] 。`は許可する。
73+
74+
text: "これは、 Exception 。",
75+
options: {
76+
space: "never",
77+
exceptPunctuation: true
78+
}
79+
80+
5581
## Changelog
5682

5783
See [Releases page](https://github.com/textlint-ja/textlint-rule-spacing/releases).

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

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,50 +6,80 @@ const assert = require("assert");
66
*/
77
import {RuleHelper} from "textlint-rule-helper";
88
import {matchCaptureGroupAll} from "match-index";
9+
const PunctuationRegExp = /[]/;
910
const defaultOptions = {
1011
// スペースを入れるかどうか
1112
// "never" or "always"
12-
"space": "never"
13+
space: "never",
14+
// [。、,.]を例外とするかどうか
15+
exceptPunctuation: true
1316
};
1417
function reporter(context, options = {}) {
1518
const {Syntax, RuleError, report, fixer, getSource} = context;
1619
const helper = new RuleHelper();
1720
const spaceOption = options.space || defaultOptions.space;
21+
const exceptPunctuation = options.exceptPunctuation !== undefined
22+
? options.exceptPunctuation
23+
: defaultOptions.exceptPunctuation;
1824
assert(spaceOption === "always" || spaceOption === "never", `"space" options should be "always" or "never".`);
19-
// アルファベットと全角の間はスペースを入れない
25+
/**
26+
* `text`を対象に例外オプションを取り除くfilter関数を返す
27+
* @param {string} text テスト対象のテキスト全体
28+
* @param {number} padding +1 or -1
29+
* @returns {function(*, *)}
30+
*/
31+
const createFilter = (text, padding) => {
32+
/**
33+
* `exceptPunctuation`で指定された例外を取り除く
34+
* @param {Object} match
35+
* @returns {boolean}
36+
*/
37+
return (match) => {
38+
const targetChar = text[match.index + padding];
39+
if (!targetChar) {
40+
return false;
41+
}
42+
if (exceptPunctuation && PunctuationRegExp.test(targetChar)) {
43+
return false;
44+
}
45+
return true;
46+
}
47+
};
48+
// Never: アルファベットと全角の間はスペースを入れない
2049
const noSpaceBetween = (node, text) => {
21-
const betweenHanAndZen = matchCaptureGroupAll(text, /[A-Za-z0-9]([  ])(?:[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[--])/);
22-
const betweenZenAndHan = matchCaptureGroupAll(text, /(?:[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[--])([  ])[A-Za-z0-9]/);
50+
const betweenHanAndZen = matchCaptureGroupAll(text, /[A-Za-z0-9]([  ])(?:[]|[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[--])/);
51+
const betweenZenAndHan = matchCaptureGroupAll(text, /(?:[]|[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[--])([  ])[A-Za-z0-9]/);
2352
const reportMatch = (match) => {
2453
const {index} = match;
2554
report(node, new RuleError("原則として、全角文字と半角文字の間にスペースを入れません。", {
2655
index: match.index,
2756
fix: fixer.replaceTextRange([index, index + 1], "")
2857
}));
2958
};
30-
betweenHanAndZen.forEach(reportMatch);
31-
betweenZenAndHan.forEach(reportMatch);
59+
betweenHanAndZen.filter(createFilter(text, 1)).forEach(reportMatch);
60+
betweenZenAndHan.filter(createFilter(text, -1)).forEach(reportMatch);
3261
};
3362

34-
// アルファベットと全角の間はスペースを入れる
63+
// Always: アルファベットと全角の間はスペースを入れる
3564
const needSpaceBetween = (node, text) => {
36-
const betweenHanAndZen = matchCaptureGroupAll(text, /[A-Za-z0-9]([^  ])(?:[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[--])/);
37-
const betweenZenAndHan = matchCaptureGroupAll(text, /(?:[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[--])([^  ])[A-Za-z0-9]/);
65+
const betweenHanAndZen = matchCaptureGroupAll(text, /([A-Za-z0-9])(?:[]|[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[--])/);
66+
const betweenZenAndHan = matchCaptureGroupAll(text, /([]|[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[--])[A-Za-z0-9]/);
3867
const reportMatch = (match) => {
3968
const {index} = match;
4069
report(node, new RuleError("原則として、全角文字と半角文字の間にスペースを入れます。", {
4170
index: match.index,
4271
fix: fixer.replaceTextRange([index + 1, index + 1], " ")
4372
}));
4473
};
45-
betweenHanAndZen.forEach(reportMatch);
46-
betweenZenAndHan.forEach(reportMatch);
74+
betweenHanAndZen.filter(createFilter(text, 1)).forEach(reportMatch);
75+
betweenZenAndHan.filter(createFilter(text, 0)).forEach(reportMatch);
4776
};
4877
return {
4978
[Syntax.Str](node){
50-
if (helper.isChildNode(node, [
51-
Syntax.Header, Syntax.Link, Syntax.Image, Syntax.BlockQuote, Syntax.Emphasis
52-
])) {
79+
const isIgnoredParentNode = helper.isChildNode(node, [
80+
Syntax.Header, Syntax.Link, Syntax.Image, Syntax.BlockQuote, Syntax.Emphasis
81+
]);
82+
if (isIgnoredParentNode) {
5383
return;
5484
}
5585
const text = getSource(node);

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

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import rule from "../src/index";
55
var tester = new TextLintTester();
66
tester.run("全角文字と半角文字の間", rule, {
77
valid: [
8-
// never
8+
// デフォルト: never && exceptPunctuation
99
"JTF標準",
10+
"これも、OK。",
1011
{
1112
text: "JTF標準",
1213
options: {
@@ -43,6 +44,37 @@ Pull Request、コミットのやりかたなどが書かれています。`,
4344
space: "never"
4445
},
4546
},
47+
// except
48+
{
49+
text: "Always これは、Exception。",
50+
options: {
51+
space: "always",
52+
exceptPunctuation: true
53+
},
54+
},
55+
// 入れても良い
56+
{
57+
text: "Always これは 、 Exception 。",
58+
options: {
59+
space: "always",
60+
exceptPunctuation: true
61+
},
62+
},
63+
{
64+
text: "Never:これは、 Exception 。",
65+
options: {
66+
space: "never",
67+
exceptPunctuation: true
68+
}
69+
},
70+
// 入れても良い
71+
{
72+
text: "Never:これは、Exception。",
73+
options: {
74+
space: "never",
75+
exceptPunctuation: true
76+
}
77+
},
4678
],
4779
invalid: [
4880
{
@@ -82,6 +114,33 @@ Pull Request、コミットのやりかたなどが書かれています。`,
82114
{message: "原則として、全角文字と半角文字の間にスペースを入れません。"}
83115
]
84116
},
117+
{
118+
119+
text: "aaa と bbb 、 ccc と ddd",
120+
output: "aaaとbbb 、 cccとddd",
121+
options: {
122+
space: "never",
123+
exceptPunctuation: true
124+
},
125+
errors: [
126+
{
127+
message: "原則として、全角文字と半角文字の間にスペースを入れません。",
128+
column: 4
129+
},
130+
{
131+
message: "原則として、全角文字と半角文字の間にスペースを入れません。",
132+
column: 6
133+
},
134+
{
135+
message: "原則として、全角文字と半角文字の間にスペースを入れません。",
136+
column: 16
137+
},
138+
{
139+
message: "原則として、全角文字と半角文字の間にスペースを入れません。",
140+
column: 18
141+
}
142+
]
143+
},
85144
// need space
86145
{
87146
text: "JTF標準",
@@ -96,6 +155,24 @@ Pull Request、コミットのやりかたなどが書かれています。`,
96155
}
97156
]
98157
},
158+
{
159+
text: "aaa、bbb",
160+
output: "aaa 、 bbb",
161+
options: {
162+
space: "always",
163+
exceptPunctuation: false
164+
},
165+
errors: [
166+
{
167+
message: "原則として、全角文字と半角文字の間にスペースを入れます。",
168+
column: 3
169+
},
170+
{
171+
message: "原則として、全角文字と半角文字の間にスペースを入れます。",
172+
column: 4
173+
}
174+
]
175+
},
99176
{
100177
text: "これはUnicode",
101178
output: "これは Unicode",
@@ -126,5 +203,32 @@ Pull Request、コミットのやりかたなどが書かれています。`,
126203
}
127204
]
128205
},
206+
// with option
207+
{
208+
text: "aaaとbbb、cccとddd",
209+
output: "aaa と bbb、ccc と ddd",
210+
options: {
211+
space: "always",
212+
exceptPunctuation: true
213+
},
214+
errors: [
215+
{
216+
message: "原則として、全角文字と半角文字の間にスペースを入れます。",
217+
column: 3
218+
},
219+
{
220+
message: "原則として、全角文字と半角文字の間にスペースを入れます。",
221+
column: 4
222+
},
223+
{
224+
message: "原則として、全角文字と半角文字の間にスペースを入れます。",
225+
column: 11
226+
},
227+
{
228+
message: "原則として、全角文字と半角文字の間にスペースを入れます。",
229+
column: 12
230+
}
231+
]
232+
},
129233
]
130234
});

0 commit comments

Comments
 (0)