@@ -7,26 +7,58 @@ const assert = require("assert");
7
7
import { RuleHelper } from "textlint-rule-helper" ;
8
8
import { matchCaptureGroupAll } from "match-index" ;
9
9
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
+ } ;
10
16
const defaultOptions = {
11
- // スペースを入れるかどうか
12
- // "never" or "always"
13
- space : "never" ,
14
- // [。、,.]を例外とするかどうか
15
- exceptPunctuation : true ,
16
17
// プレーンテキスト以外を対象とするか See https://github.com/textlint/textlint-rule-helper#rulehelperisplainstrnodenode-boolean
17
18
lintStyledNode : false ,
18
19
} ;
19
20
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
+
20
56
const { Syntax, RuleError, report, fixer, getSource} = context ;
21
57
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 ) ;
26
59
const lintStyledNode = options . lintStyledNode !== undefined
27
60
? options . lintStyledNode
28
61
: defaultOptions . lintStyledNode ;
29
- assert ( spaceOption === "always" || spaceOption === "never" , `"space" options should be "always" or "never".` ) ;
30
62
/**
31
63
* `text`を対象に例外オプションを取り除くfilter関数を返す
32
64
* @param {string } text テスト対象のテキスト全体
@@ -35,7 +67,7 @@ function reporter(context, options = {}) {
35
67
*/
36
68
const createFilter = ( text , padding ) => {
37
69
/**
38
- * `exceptPunctuation `で指定された例外を取り除く
70
+ * `PunctuationRegExp `で指定された例外を取り除く
39
71
* @param {Object } match
40
72
* @returns {boolean }
41
73
*/
@@ -44,16 +76,16 @@ function reporter(context, options = {}) {
44
76
if ( ! targetChar ) {
45
77
return false ;
46
78
}
47
- if ( exceptPunctuation && PunctuationRegExp . test ( targetChar ) ) {
79
+ if ( ! spaceOption . punctuation && PunctuationRegExp . test ( targetChar ) ) {
48
80
return false ;
49
81
}
50
82
return true ;
51
83
}
52
84
} ;
53
85
// Never: アルファベットと全角の間はスペースを入れない
54
86
const noSpaceBetween = ( node , text ) => {
55
- const betweenHanAndZen = matchCaptureGroupAll ( text , / [ A - Z a - z 0 - 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 - Z a - z 0 - 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]` ) ) ;
57
89
const reportMatch = ( match ) => {
58
90
const { index} = match ;
59
91
report ( node , new RuleError ( "原則として、全角文字と半角文字の間にスペースを入れません。" , {
@@ -66,12 +98,36 @@ function reporter(context, options = {}) {
66
98
} ;
67
99
68
100
// Always: アルファベットと全角の間はスペースを入れる
69
- const needSpaceBetween = ( node , text ) => {
70
- const betweenHanAndZen = matchCaptureGroupAll ( text , / ( [ A - Z a - z 0 - 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 - Z a - z 0 - 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 ) ;
72
128
const reportMatch = ( match ) => {
73
129
const { index} = match ;
74
- report ( node , new RuleError ( "原則として、全角文字と半角文字の間にスペースを入れます。" , {
130
+ report ( node , new RuleError ( errorMsg , {
75
131
index : match . index ,
76
132
fix : fixer . replaceTextRange ( [ index + 1 , index + 1 ] , " " )
77
133
} ) ) ;
@@ -86,12 +142,12 @@ function reporter(context, options = {}) {
86
142
}
87
143
const text = getSource ( node ) ;
88
144
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 ) ) {
92
147
noSpaceBetween ( node , text ) ;
148
+ } else {
149
+ needSpaceBetween ( node , text , spaceOption ) ;
93
150
}
94
-
95
151
}
96
152
}
97
153
}
0 commit comments