11// LICENSE : MIT
22"use strict" ;
3- import { RuleHelper } from "textlint-rule-helper"
3+ import { RuleHelper } from "textlint-rule-helper" ;
44import { getTokenizer } from "kuromojin" ;
55import { splitAST , Syntax as SentenceSyntax } from "sentence-splitter" ;
66import { StringSource } from "textlint-util-to-string" ;
77
88const defaultOptions = {
9- max : 3 , // 1文に利用できる最大の、の数
10- strict : false // 例外ルールを適応するかどうか
9+ // 1文に利用できる最大の、の数
10+ max : 3 ,
11+ // 例外ルールを適応するかどうか,
12+ strict : false ,
13+ // 読点として扱う文字
14+ // https://ja.wikipedia.org/wiki/%E8%AA%AD%E7%82%B9
15+ touten : "、" ,
16+ // 句点として扱う文字
17+ // https://ja.wikipedia.org/wiki/%E5%8F%A5%E7%82%B9
18+ kuten : "。"
1119} ;
1220
13- function isSandwichedMeishi ( {
14- before,
15- token,
16- after
17- } ) {
21+ function isSandwichedMeishi ( { before, token, after } ) {
1822 if ( before === undefined || after === undefined || token === undefined ) {
1923 return false ;
2024 }
@@ -23,20 +27,31 @@ function isSandwichedMeishi({
2327
2428/**
2529 * @param {RuleContext } context
26- * @param {object } [options]
30+ * @param {typeof defaultOptions } [options]
2731 */
2832module . exports = function ( context , options = { } ) {
29- const maxLen = options . max || defaultOptions . max ;
30- const isStrict = options . strict || defaultOptions . strict ;
33+ const maxLen = options . max ?? defaultOptions . max ;
34+ const isStrict = options . strict ?? defaultOptions . strict ;
35+ const touten = options . touten ?? defaultOptions . touten ;
36+ const kuten = options . kuten ?? defaultOptions . kuten ;
3137 const helper = new RuleHelper ( context ) ;
3238 const { Syntax, RuleError, report, getSource } = context ;
3339 return {
3440 [ Syntax . Paragraph ] ( node ) {
3541 if ( helper . isChildNode ( node , [ Syntax . BlockQuote ] ) ) {
3642 return ;
3743 }
38- const resultNode = splitAST ( node ) ;
39- const sentences = resultNode . children . filter ( childNode => childNode . type === SentenceSyntax . Sentence ) ;
44+ const resultNode = splitAST ( node , {
45+ SeparatorParser : {
46+ separatorCharacters : [
47+ "?" , // question mark
48+ "!" , // exclamation mark
49+ "?" , // (ja) zenkaku question mark
50+ "!" // (ja) zenkaku exclamation mark
51+ ] . concat ( kuten )
52+ }
53+ } ) ;
54+ const sentences = resultNode . children . filter ( ( childNode ) => childNode . type === SentenceSyntax . Sentence ) ;
4055 /*
4156 <p>
4257 <str><code><img><str>
@@ -49,18 +64,18 @@ module.exports = function (context, options = {}) {
4964 2. sentence to tokens
5065 3. check tokens
5166 */
52- return getTokenizer ( ) . then ( tokenizer => {
53- sentences . forEach ( sentence => {
67+ return getTokenizer ( ) . then ( ( tokenizer ) => {
68+ sentences . forEach ( ( sentence ) => {
5469 const source = new StringSource ( sentence ) ;
5570 const text = source . toString ( ) ;
5671 const tokens = tokenizer . tokenizeForSentence ( text ) ;
5772 let currentTenCount = 0 ;
5873 let lastToken = null ;
5974 tokens . forEach ( ( token , index ) => {
60- let surface = token . surface_form ;
61- if ( surface === "、" ) {
75+ const surface = token . surface_form ;
76+ if ( surface === touten ) {
6277 // 名詞に囲まわれている場合は例外とする
63- let isSandwiched = isSandwichedMeishi ( {
78+ const isSandwiched = isSandwichedMeishi ( {
6479 before : tokens [ index - 1 ] ,
6580 token : token ,
6681 after : tokens [ index + 1 ]
@@ -80,15 +95,18 @@ module.exports = function (context, options = {}) {
8095 if ( currentTenCount >= maxLen ) {
8196 const positionInSentence = source . originalIndexFromIndex ( lastToken . word_position - 1 ) ;
8297 const index = sentence . range [ 0 ] + positionInSentence ;
83- const ruleError = new context . RuleError ( `一つの文で"、"を${ maxLen } つ以上使用しています` , {
84- index
85- } ) ;
98+ const ruleError = new context . RuleError (
99+ `一つの文で"${ touten } "を${ maxLen } つ以上使用しています` ,
100+ {
101+ index
102+ }
103+ ) ;
86104 report ( node , ruleError ) ;
87105 currentTenCount = 0 ;
88106 }
89107 } ) ;
90108 } ) ;
91109 } ) ;
92110 }
93- }
94- }
111+ } ;
112+ } ;
0 commit comments