11// LICENSE : MIT
22"use strict" ;
33const RuleHelper = require ( "textlint-rule-helper" ) . RuleHelper ;
4- const emojiRegExp = require ( "emoji-regex" ) ( ) ;
54const 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" ) ;
812const defaultOptions = {
913 // 優先する句点文字
1014 periodMark : "。" ,
15+ // 句点文字として許可する文字列の配列
16+ // 例外として許可したい文字列を設定する
17+ // `periodMark`に指定したものは自動的に許可リストに加わる
18+ allowPeriodMarks : [ ] ,
1119 // 末尾に絵文字を置くことを許可するか
12- allowEmojiAtEnd : false
20+ allowEmojiAtEnd : false ,
21+ // 句点で終わって無い場合に`periodMark`を--fix時に追加するかどうか
22+ // デフォルトでは自動的に追加しない
23+ forceAppendPeriod : false
1324} ;
1425const reporter = ( context , options = { } ) => {
15- const { Syntax, RuleError, report, fixer, getSource} = context ;
26+ const { Syntax, RuleError, report, fixer, getSource } = context ;
1627 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+ ] ;
2042 return {
2143 [ Syntax . Paragraph ] ( node ) {
2244 if ( helper . isChildNode ( node , ignoredNodeTypes ) ) {
@@ -27,52 +49,50 @@ const reporter = (context, options = {}) => {
2749 return ;
2850 }
2951 const lastStrText = getSource ( lastNode ) ;
52+ if ( lastStrText . length === 0 ) {
53+ return ;
54+ }
3055 // 日本語が含まれていない文章は無視する
3156 if ( ! japaneseRegExp . test ( lastStrText ) ) {
3257 return ;
3358 }
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 ) {
3965 return ;
4066 }
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 ] , "" )
4672 } ) ) ;
4773 return
4874 }
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+ } ) ) ;
6882 } 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+ }
7095 }
71- report ( lastNode , new RuleError ( `文末が"${ periodMark } "で終わっていません。` , {
72- index : lastIndex ,
73- fix
74- } ) ) ;
75-
7696 }
7797 }
7898} ;
0 commit comments