@@ -4,68 +4,79 @@ const assert = require("assert");
4
4
/*
5
5
全角文字と半角文字の間にスペースを入れるかどうか
6
6
*/
7
- import { RuleHelper } from "textlint-rule-helper" ;
8
- import { matchCaptureGroupAll } from "match-index" ;
7
+ import { RuleHelper } from "textlint-rule-helper" ;
8
+ import { matchCaptureGroupAll } from "match-index" ;
9
+ import { matchPatterns } from "@textlint/regexp-string-matcher" ;
10
+
9
11
const PunctuationRegExp = / [ 。 、 ] / ;
10
12
const ZenRegExpStr = '[、。]|[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[ぁ-んァ-ヶ]' ;
11
13
const defaultSpaceOptions = {
12
14
alphabets : false ,
13
15
numbers : false ,
14
- punctuation : false
16
+ punctuation : false ,
15
17
} ;
16
18
const defaultOptions = {
17
19
// プレーンテキスト以外を対象とするか See https://github.com/textlint/textlint-rule-helper#rulehelperisplainstrnodenode-boolean
18
20
lintStyledNode : false ,
21
+ /**
22
+ * 例外として無視する文字列
23
+ * RegExp-like Stringの配列を指定
24
+ * https://github.com/textlint/regexp-string-matcher?tab=readme-ov-file#regexp-like-string
25
+ */
26
+ allows : [ ]
19
27
} ;
28
+
20
29
function reporter ( context , options = { } ) {
21
- /**
22
- * 入力された `space` オプションを内部処理用に成形する
23
- * @param {string|Array|undefined } opt `space` オプションのインプット
24
- * @param {boolean|undefined } exceptPunctuation `exceptPunctuation` オプションのインプット
25
- * @returns {Object }
26
- */
30
+ /**
31
+ * 入力された `space` オプションを内部処理用に成形する
32
+ * @param {string|Array|undefined } opt `space` オプションのインプット
33
+ * @param {boolean|undefined } exceptPunctuation `exceptPunctuation` オプションのインプット
34
+ * @returns {Object }
35
+ */
27
36
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
- }
37
+ if ( typeof opt === 'string' ) {
38
+ assert ( opt === "always" || opt === "never" , `"space" options should be "always", "never" or an array.` ) ;
39
+
40
+ if ( opt === "always" ) {
41
+ if ( exceptPunctuation === false ) {
42
+ return { ...defaultSpaceOptions , alphabets : true , numbers : true , punctuation : true } ;
43
+ } else {
44
+ return { ...defaultSpaceOptions , alphabets : true , numbers : true } ;
45
+ }
46
+ } else if ( opt === "never" ) {
47
+ if ( exceptPunctuation === false ) {
48
+ return { ...defaultSpaceOptions , punctuation : true } ;
49
+ } else {
50
+ return defaultSpaceOptions ;
51
+ }
52
+ }
53
+ } else if ( Array . isArray ( opt ) ) {
54
+ assert (
55
+ opt . every ( ( v ) => Object . keys ( defaultSpaceOptions ) . includes ( v ) ) ,
56
+ `Only "alphabets", "numbers", "punctuation" can be included in the array.`
57
+ ) ;
58
+ const userOptions = Object . fromEntries ( opt . map ( key => [ key , true ] ) ) ;
59
+ return { ...defaultSpaceOptions , ...userOptions } ;
43
60
}
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 ;
61
+
62
+ return defaultSpaceOptions ;
54
63
}
55
-
56
- const { Syntax, RuleError, report, fixer, getSource} = context ;
64
+
65
+ const { Syntax, RuleError, report, fixer, getSource } = context ;
57
66
const helper = new RuleHelper ( ) ;
58
67
const spaceOption = parseSpaceOption ( options . space , options . exceptPunctuation ) ;
59
68
const lintStyledNode = options . lintStyledNode !== undefined
60
69
? options . lintStyledNode
61
70
: defaultOptions . lintStyledNode ;
71
+ const allows = options . allows !== undefined ? options . allows : defaultOptions . allows ;
62
72
/**
63
73
* `text`を対象に例外オプションを取り除くfilter関数を返す
64
74
* @param {string } text テスト対象のテキスト全体
65
75
* @param {number } padding +1 or -1
66
76
* @returns {function(*, *) }
67
77
*/
68
78
const createFilter = ( text , padding ) => {
79
+ const allowedPatterns = allows . length > 0 ? matchPatterns ( text , allows ) : [ ] ;
69
80
/**
70
81
* `PunctuationRegExp`で指定された例外を取り除く
71
82
* @param {Object } match
@@ -79,15 +90,22 @@ function reporter(context, options = {}) {
79
90
if ( ! spaceOption . punctuation && PunctuationRegExp . test ( targetChar ) ) {
80
91
return false ;
81
92
}
82
- return true ;
93
+ const isAllowed = allowedPatterns . some ( ( allow ) => {
94
+ // start ... end
95
+ if ( allow . startIndex <= match . index && match . index <= allow . endIndex ) {
96
+ return true ;
97
+ }
98
+ return false
99
+ } )
100
+ return ! isAllowed ;
83
101
}
84
102
} ;
85
103
// Never: アルファベットと全角の間はスペースを入れない
86
104
const noSpaceBetween = ( node , text ) => {
87
105
const betweenHanAndZen = matchCaptureGroupAll ( text , new RegExp ( `[A-Za-z0-9]([ ])(?:${ ZenRegExpStr } )` ) ) ;
88
106
const betweenZenAndHan = matchCaptureGroupAll ( text , new RegExp ( `(?:${ ZenRegExpStr } )([ ])[A-Za-z0-9]` ) ) ;
89
107
const reportMatch = ( match ) => {
90
- const { index} = match ;
108
+ const { index } = match ;
91
109
report ( node , new RuleError ( "原則として、全角文字と半角文字の間にスペースを入れません。" , {
92
110
index : match . index ,
93
111
fix : fixer . replaceTextRange ( [ index , index + 1 ] , "" )
@@ -96,37 +114,37 @@ function reporter(context, options = {}) {
96
114
betweenHanAndZen . filter ( createFilter ( text , 1 ) ) . forEach ( reportMatch ) ;
97
115
betweenZenAndHan . filter ( createFilter ( text , - 1 ) ) . forEach ( reportMatch ) ;
98
116
} ;
99
-
117
+
100
118
// Always: アルファベットと全角の間はスペースを入れる
101
119
const needSpaceBetween = ( node , text , options ) => {
102
- /**
103
- * オプションを元に正規表現オプジェクトを生成する
104
- * @param {Array } opt `space` オプション
105
- * @param {boolean } btwHanAndZen=true 半角全角の間か全角半角の間か
106
- * @returns {Object }
107
- */
120
+ /**
121
+ * オプションを元に正規表現オプジェクトを生成する
122
+ * @param {Array } opt `space` オプション
123
+ * @param {boolean } btwHanAndZen=true 半角全角の間か全角半角の間か
124
+ * @returns {Object }
125
+ */
108
126
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 ) ;
127
+ const alphabets = opt . alphabets ? 'A-Za-z' : '' ;
128
+ const numbers = opt . numbers ? '0-9' : '' ;
129
+
130
+ let expStr ;
131
+ if ( btwHanAndZen ) {
132
+ expStr = `([${ alphabets } ${ numbers } ])(?:${ ZenRegExpStr } )` ;
133
+ } else {
134
+ expStr = `(${ ZenRegExpStr } )[${ alphabets } ${ numbers } ]` ;
135
+ }
136
+
137
+ return new RegExp ( expStr ) ;
120
138
} ;
121
-
139
+
122
140
const betweenHanAndZenRegExp = generateRegExp ( options ) ;
123
141
const betweenZenAndHanRegExp = generateRegExp ( options , false ) ;
124
142
const errorMsg = '原則として、全角文字と半角文字の間にスペースを入れます。' ;
125
-
143
+
126
144
const betweenHanAndZen = matchCaptureGroupAll ( text , betweenHanAndZenRegExp ) ;
127
145
const betweenZenAndHan = matchCaptureGroupAll ( text , betweenZenAndHanRegExp ) ;
128
146
const reportMatch = ( match ) => {
129
- const { index} = match ;
147
+ const { index } = match ;
130
148
report ( node , new RuleError ( errorMsg , {
131
149
index : match . index ,
132
150
fix : fixer . replaceTextRange ( [ index + 1 , index + 1 ] , " " )
@@ -136,12 +154,12 @@ function reporter(context, options = {}) {
136
154
betweenZenAndHan . filter ( createFilter ( text , 0 ) ) . forEach ( reportMatch ) ;
137
155
} ;
138
156
return {
139
- [ Syntax . Str ] ( node ) {
157
+ [ Syntax . Str ] ( node ) {
140
158
if ( ! lintStyledNode && ! helper . isPlainStrNode ( node ) ) {
141
159
return ;
142
160
}
143
161
const text = getSource ( node ) ;
144
-
162
+
145
163
const noSpace = ( key ) => key === 'punctuation' ? true : ! spaceOption [ key ] ;
146
164
if ( Object . keys ( spaceOption ) . every ( noSpace ) ) {
147
165
noSpaceBetween ( node , text ) ;
@@ -151,6 +169,7 @@ function reporter(context, options = {}) {
151
169
}
152
170
}
153
171
}
172
+
154
173
module . exports = {
155
174
linter : reporter ,
156
175
fixer : reporter
0 commit comments