Skip to content

Commit 8ee0fdc

Browse files
authored
Merge pull request #10 from textlint-ja/fix/7
feat(options): add 読点 and 句点 as options
2 parents 2bc5335 + 9758b2d commit 8ee0fdc

File tree

6 files changed

+616
-54
lines changed

6 files changed

+616
-54
lines changed

.githook/pre-commit

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/bin/sh
2+
npx --no-install lint-staged

README.md

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44

55
一文に利用できる``の数を制限する[textlint](https://github.com/textlint/textlint "textlint")ルール
66

7-
一文の読点の数が多いと冗長で読みにくい文章となるため、読点の数を一定数以下にするルールです。
8-
読点の数を減らすためには、句点(。)で文を区切る必要があります。
7+
一文の読点の数が多いと冗長で読みにくい文章となるため、読点の数を一定数以下にするルールです。 読点の数を減らすためには、句点(。)で文を区切る必要があります。
98

109
## Installation
1110

@@ -17,26 +16,49 @@
1716
$ textlint --rule max-ten README.md
1817
# 11:0 error 一つの文で"、"を3つ以上使用しています max-ten
1918

20-
## Setting
19+
## Options
2120

2221
- `max`: number
2322
- デフォルト: 3
24-
- 一文に許可される読点の数 + 1
23+
- 一文に許可される読点の数 + 1となった場合にエラーとします。デフォルトでは4つの"、"が一文にあるとエラーとなります。
2524

26-
```json
25+
```json5
2726
{
2827
"rules": {
2928
"max-ten": {
30-
"max" : 3
29+
// 1文に利用できる最大の、の数
30+
"max": 3,
31+
// 例外ルールを適応するかどうか,
32+
"strict": false,
33+
// 読点として扱う文字
34+
// https://ja.wikipedia.org/wiki/%E8%AA%AD%E7%82%B9
35+
"touten": "",
36+
// 句点として扱う文字
37+
// https://ja.wikipedia.org/wiki/%E5%8F%A5%E7%82%B9
38+
"kuten": ""
39+
}
40+
}
41+
}
42+
```
43+
44+
読点に「,」句点に「.」を使う場合は、次のように設定します。
45+
46+
```json5
47+
{
48+
"rules": {
49+
"max-ten": {
50+
// 読点として扱う文字
51+
"touten": "",
52+
// 句点として扱う文字
53+
"kuten": ""
3154
}
3255
}
3356
}
3457
```
3558

3659
## 例外
3760

38-
`<名詞>``<名詞>` のように名詞に挟まれた読点はカウントしません。
39-
箇条書きとしての区切り文字として使われているため無視します。
61+
`<名詞>``<名詞>` のように名詞に挟まれた読点はカウントしません。 箇条書きとしての区切り文字として使われているため無視します。
4062

4163
## Tests
4264

package.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,13 @@
3030
"build": "textlint-scripts build",
3131
"watch": "textlint-scripts build --watch",
3232
"prepublish": "npm run --if-present build",
33-
"test": "textlint-scripts test"
33+
"test": "textlint-scripts test",
34+
"prettier": "prettier --write \"**/*.{js,jsx,ts,tsx,css}\"",
35+
"prepare": "git config --local core.hooksPath .githook"
3436
},
3537
"devDependencies": {
38+
"lint-staged": "^10.5.4",
39+
"prettier": "^2.2.1",
3640
"textlint-scripts": "^3.0.0"
3741
},
3842
"dependencies": {
@@ -41,5 +45,16 @@
4145
"structured-source": "^3.0.2",
4246
"textlint-rule-helper": "^2.0.0",
4347
"textlint-util-to-string": "^3.1.1"
48+
},
49+
"prettier": {
50+
"singleQuote": false,
51+
"printWidth": 120,
52+
"tabWidth": 4,
53+
"trailingComma": "none"
54+
},
55+
"lint-staged": {
56+
"*.{js,jsx,ts,tsx,css}": [
57+
"prettier --write"
58+
]
4459
}
4560
}

src/max-ten.js

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
// LICENSE : MIT
22
"use strict";
3-
import { RuleHelper } from "textlint-rule-helper"
3+
import { RuleHelper } from "textlint-rule-helper";
44
import { getTokenizer } from "kuromojin";
55
import { splitAST, Syntax as SentenceSyntax } from "sentence-splitter";
66
import { StringSource } from "textlint-util-to-string";
77

88
const 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
*/
2832
module.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+
};

test/max-ten-test.js

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
const rule = require("../src/max-ten");
1+
import TextLintTester from "textlint-tester";
2+
import rule from "../src/max-ten";
3+
24
function textIncludeTen(count) {
3-
return (new Array(count + 1)).join("テスト文章において、") + "です";
5+
return new Array(count + 1).join("テスト文章において、") + "です";
46
}
5-
var TextLintTester = require("textlint-tester");
6-
var tester = new TextLintTester();
7+
8+
const tester = new TextLintTester();
79
// ruleName, rule, expected[]
810
tester.run("max-ten", rule, {
911
// default max:3
@@ -17,19 +19,31 @@ tester.run("max-ten", rule, {
1719
{
1820
text: textIncludeTen(5 - 1),
1921
options: {
20-
"max": 5
22+
max: 5
2123
}
2224
},
2325
{
2426
text: "これは、テストです。"
27+
},
28+
{
29+
text: "これは、これは、これは、これは、オプションでカウントされないのでOK",
30+
options: {
31+
touten: ",",
32+
kuten: "."
33+
}
34+
},
35+
{
36+
text: `これは,これは.これは,これは.`,
37+
options: {
38+
touten: ",",
39+
kuten: "."
40+
}
2541
}
26-
2742
],
2843
invalid: [
2944
{
3045
text: `これは、これは、これは
31-
、d`
32-
,
46+
、d`,
3347
errors: [
3448
{
3549
message: `一つの文で"、"を3つ以上使用しています`,
@@ -38,10 +52,36 @@ tester.run("max-ten", rule, {
3852
}
3953
]
4054
},
55+
{
56+
text: `これは,これは,これは,これは。`,
57+
errors: [
58+
{
59+
message: `一つの文で","を3つ以上使用しています`,
60+
index: 11
61+
}
62+
],
63+
options: {
64+
touten: ",",
65+
kuten: "."
66+
}
67+
},
68+
{
69+
text: `これは,これは,これは。これは,これは,これは,`,
70+
errors: [
71+
{
72+
message: `一つの文で","を3つ以上使用しています`,
73+
index: 23
74+
}
75+
],
76+
options: {
77+
touten: ",",
78+
kuten: "."
79+
}
80+
},
4181
{
4282
text: textIncludeTen(5),
4383
options: {
44-
"max": 5
84+
max: 5
4585
},
4686
errors: [
4787
{
@@ -52,7 +92,7 @@ tester.run("max-ten", rule, {
5292
{
5393
text: `これは、長文の例ですが、columnがちゃんと計算、されてるはずです。`,
5494
options: {
55-
"max": 3
95+
max: 3
5696
},
5797
errors: [
5898
{
@@ -65,7 +105,7 @@ tester.run("max-ten", rule, {
65105
{
66106
text: "間に、Str以外の`code`Nodeが、あっても、OK",
67107
options: {
68-
"max": 3
108+
max: 3
69109
},
70110
errors: [
71111
{
@@ -78,7 +118,7 @@ tester.run("max-ten", rule, {
78118
{
79119
text: `複数のセンテンスがある場合。これでも、columnが、ちゃんと計算、されているはずです。`,
80120
options: {
81-
"max": 3
121+
max: 3
82122
},
83123
errors: [
84124
{
@@ -91,7 +131,7 @@ tester.run("max-ten", rule, {
91131
{
92132
text: `複数のセンテンスがあって、改行されている場合でも\n大丈夫です。これでも、lineとcolumnが、ちゃんと計算、されているはずです。`,
93133
options: {
94-
"max": 3
134+
max: 3
95135
},
96136
errors: [
97137
{
@@ -102,4 +142,4 @@ tester.run("max-ten", rule, {
102142
]
103143
}
104144
]
105-
});
145+
});

0 commit comments

Comments
 (0)