1
1
// LICENSE : MIT
2
2
"use strict" ;
3
3
const RuleHelper = require ( "textlint-rule-helper" ) . RuleHelper ;
4
- const emojiRegExp = require ( "emoji-regex" ) ( ) ;
5
4
const japaneseRegExp = / (?: [ 々 〇 〻 \u3400 - \u4DBF \u4E00 - \u9FFF \uF900 - \uFAFF ] | [ \uD840 - \uD87F ] [ \uDC00 - \uDFFF ] | [ ぁ - ん ァ - ヶ ] ) / ;
6
- const exceptionMarkRegExp = / [ ! ? ! ? \) ) 」 』 ] / ;
7
- const defaultPeriodMark = / [ 。 \. ] / ;
5
+ /***
6
+ * 典型的な句点のパターン
7
+ * これは`periodMark`と交換しても違和感がないものを登録
8
+ * @type {RegExp }
9
+ */
10
+ const classicPeriodMarkPattern = / [ 。 \. ] / ;
11
+ const checkEndsWithPeriod = require ( "check-ends-with-period" ) ;
8
12
const defaultOptions = {
9
13
// 優先する句点文字
10
14
periodMark : "。" ,
15
+ // 句点文字として許可する文字列の配列
16
+ // 例外として許可したい文字列を設定する
17
+ // `periodMark`に指定したものは自動的に許可リストに加わる
18
+ allowPeriodMarks : [ ] ,
11
19
// 末尾に絵文字を置くことを許可するか
12
- allowEmojiAtEnd : false
20
+ allowEmojiAtEnd : false ,
21
+ // 句点で終わって無い場合に`periodMark`を--fix時に追加するかどうか
22
+ // デフォルトでは自動的に追加しない
23
+ forceAppendPeriod : false
13
24
} ;
14
25
const reporter = ( context , options = { } ) => {
15
- const { Syntax, RuleError, report, fixer, getSource} = context ;
26
+ const { Syntax, RuleError, report, fixer, getSource } = context ;
16
27
const helper = new RuleHelper ( context ) ;
17
- const periodMark = options . periodMark || defaultOptions . periodMark ;
18
- const allowEmojiAtEnd = options . allowEmojiAtEnd !== undefined ? options . allowEmojiAtEnd : defaultOptions . allowEmojiAtEnd ;
19
- const ignoredNodeTypes = [ Syntax . ListItem , Syntax . Link , Syntax . Code , Syntax . Image , Syntax . BlockQuote , Syntax . Emphasis ] ;
28
+ // 優先する句点記号
29
+ const preferPeriodMark = options . periodMark || defaultOptions . periodMark ;
30
+ // 優先する句点記号は常に句点として許可される
31
+ const allowPeriodMarks = ( options . allowPeriodMarks || defaultOptions . allowPeriodMarks ) . concat ( preferPeriodMark ) ;
32
+ const allowEmojiAtEnd = options . allowEmojiAtEnd !== undefined
33
+ ? options . allowEmojiAtEnd
34
+ : defaultOptions . allowEmojiAtEnd ;
35
+ const forceAppendPeriod = options . forceAppendPeriod !== undefined
36
+ ? options . forceAppendPeriod
37
+ : defaultOptions . forceAppendPeriod ;
38
+
39
+ const ignoredNodeTypes = [
40
+ Syntax . ListItem , Syntax . Link , Syntax . Code , Syntax . Image , Syntax . BlockQuote , Syntax . Emphasis
41
+ ] ;
20
42
return {
21
43
[ Syntax . Paragraph ] ( node ) {
22
44
if ( helper . isChildNode ( node , ignoredNodeTypes ) ) {
@@ -27,52 +49,50 @@ const reporter = (context, options = {}) => {
27
49
return ;
28
50
}
29
51
const lastStrText = getSource ( lastNode ) ;
52
+ if ( lastStrText . length === 0 ) {
53
+ return ;
54
+ }
30
55
// 日本語が含まれていない文章は無視する
31
56
if ( ! japaneseRegExp . test ( lastStrText ) ) {
32
57
return ;
33
58
}
34
- // サロゲートペアを考慮した文字列長・文字アクセス
35
- const characters = [ ...lastStrText ] ;
36
- const lastIndex = characters . length - 1 ;
37
- const lastChar = characters [ lastIndex ] ;
38
- if ( lastChar === undefined ) {
59
+ const { valid, periodMark, index } = checkEndsWithPeriod ( lastStrText , {
60
+ periodMarks : allowPeriodMarks ,
61
+ allowEmoji : allowEmojiAtEnd
62
+ } ) ;
63
+ // 問題が無い場合は何もしない
64
+ if ( valid ) {
39
65
return ;
40
66
}
41
- // 文末がスペースである場合
42
- // TODO: fixに対応したい
43
- if ( / \s / . test ( lastChar ) ) {
44
- report ( lastNode , new RuleError ( `文末が" ${ periodMark } "で終わっていません。末尾に不要なスペースがあります。` , {
45
- index : lastIndex
67
+ // 文末がスペースである場合はスペースを削除する
68
+ if ( / \s / . test ( periodMark ) ) {
69
+ report ( lastNode , new RuleError ( `文末が" ${ preferPeriodMark } "で終わっていません。末尾に不要なスペースがあります。` , {
70
+ index ,
71
+ fix : fixer . replaceTextRange ( [ index , index + periodMark . length ] , "" )
46
72
} ) ) ;
47
73
return
48
74
}
49
- // 末尾の"文字"が句点以外で末尾に使われる文字であるときは無視する
50
- // 例外: 感嘆符
51
- // 例外: 「」 () ()『』
52
- // http://ncode.syosetu.com/n8977bb/12/
53
- // https://ja.wikipedia.org/wiki/%E7%B5%82%E6%AD%A2%E7%AC%A6
54
- if ( exceptionMarkRegExp . test ( lastChar ) ) {
55
- return ;
56
- }
57
- if ( allowEmojiAtEnd && emojiRegExp . test ( lastChar ) ) {
58
- return ;
59
- }
60
- if ( lastChar === periodMark ) {
61
- return ;
62
- }
63
- // "." であるなら "。"に変換
64
- // そうでない場合は末尾に"。"を追加する
65
- let fix = null ;
66
- if ( defaultPeriodMark . test ( lastChar ) ) {
67
- fix = fixer . replaceTextRange ( [ lastIndex , lastIndex + 1 ] , periodMark ) ;
75
+ // 典型的なパターンは自動的に`preferPeriodMark`に置き換える
76
+ // 例) "." であるなら "。"に変換
77
+ if ( classicPeriodMarkPattern . test ( periodMark ) ) {
78
+ report ( lastNode , new RuleError ( `文末が"${ preferPeriodMark } "で終わっていません。` , {
79
+ index : index ,
80
+ fix : fixer . replaceTextRange ( [ index , index + preferPeriodMark . length ] , preferPeriodMark )
81
+ } ) ) ;
68
82
} else {
69
- fix = fixer . replaceTextRange ( [ lastIndex + 1 , lastIndex + 1 ] , periodMark ) ;
83
+ // 句点を忘れているパターン
84
+ if ( forceAppendPeriod ) {
85
+ // `forceAppendPeriod`のオプションがtrueならば、自動で句点を追加する。
86
+ report ( lastNode , new RuleError ( `文末が"${ preferPeriodMark } "で終わっていません。` , {
87
+ index : index ,
88
+ fix : fixer . replaceTextRange ( [ index + 1 , index + 1 ] , preferPeriodMark )
89
+ } ) ) ;
90
+ } else {
91
+ report ( lastNode , new RuleError ( `文末が"${ preferPeriodMark } "で終わっていません。` , {
92
+ index : index
93
+ } ) ) ;
94
+ }
70
95
}
71
- report ( lastNode , new RuleError ( `文末が"${ periodMark } "で終わっていません。` , {
72
- index : lastIndex ,
73
- fix
74
- } ) ) ;
75
-
76
96
}
77
97
}
78
98
} ;
0 commit comments