@@ -7,26 +7,58 @@ const assert = require("assert");
77import { RuleHelper } from "textlint-rule-helper" ;
88import { matchCaptureGroupAll } from "match-index" ;
99const 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+ } ;
1016const 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} ;
1920function 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 - 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]` ) ) ;
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 - 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 ) ;
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