Skip to content

Commit 4838d00

Browse files
authored
fix: fix to work some rules use ?= RegExp (#12)
* test: add test for ?= RegExp * fix(prh): fix to work rule that using `?=` RegExp * fix: fix test case * test: add more test case * refactor: remove unused function
1 parent 67a43f9 commit 4838d00

File tree

5 files changed

+329
-26
lines changed

5 files changed

+329
-26
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
],
3434
"dependencies": {
3535
"prh": "^1.0.1",
36-
"structured-source": "^3.0.2",
3736
"textlint-rule-helper": "^2.0.0",
3837
"untildify": "^3.0.2"
3938
},

src/prh-rule.js

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// LICENSE : MIT
22
"use strict";
33
import {RuleHelper} from "textlint-rule-helper";
4-
import StructuredSource from "structured-source";
54
const prh = require("prh");
65
const path = require("path");
76
const untildify = require('untildify');
@@ -50,6 +49,47 @@ Please set .textlinrc:
5049
`);
5150
}
5251
};
52+
53+
/**
54+
* for each diff of changeSet
55+
* @param {ChangeSet} changeSet
56+
* @param {string} str
57+
* @param {function({
58+
matchStartIndex: number,
59+
matchEndIndex: number,
60+
actual: string
61+
expected: string
62+
})}onChangeOfMatch
63+
*/
64+
const forEachChange = (changeSet, str, onChangeOfMatch) => {
65+
const sortedDiffs = changeSet.diffs.sort(function(a, b) {
66+
return a.index - b.index;
67+
});
68+
let delta = 0;
69+
sortedDiffs.forEach(function(diff) {
70+
const result = diff.expected.replace(/\$([0-9]{1,2})/g, function(match, g1) {
71+
const index = parseInt(g1);
72+
if (index === 0 || (diff.matches.length - 1) < index) {
73+
return match;
74+
}
75+
return diff.matches[index] || "";
76+
});
77+
// matchStartIndex/matchEndIndex value is original position, not replaced position
78+
// textlint use original position
79+
const matchStartIndex = diff.index;
80+
const matchEndIndex = matchStartIndex + diff.matches[0].length;
81+
// actual => expected
82+
const actual = str.slice(diff.index + delta, diff.index + delta + diff.matches[0].length);
83+
onChangeOfMatch({
84+
matchStartIndex,
85+
matchEndIndex,
86+
actual: actual,
87+
expected: result
88+
});
89+
str = str.slice(0, diff.index + delta) + result + str.slice(diff.index + delta + diff.matches[0].length);
90+
delta += result.length - diff.matches[0].length;
91+
});
92+
};
5393
function reporter(context, options = {}) {
5494
assertOptions(options);
5595
const textlintRcFilePath = context.config ? context.config.configFile : null;
@@ -69,33 +109,17 @@ function reporter(context, options = {}) {
69109
if (helper.isChildNode(node, [Syntax.Link, Syntax.Image, Syntax.BlockQuote, Syntax.Emphasis])) {
70110
return;
71111
}
72-
let text = getSource(node);
112+
const text = getSource(node);
73113
// to get position from index
74-
let src = new StructuredSource(text);
75-
let makeChangeSet = prhEngine.makeChangeSet(null, text);
76-
makeChangeSet.diffs.forEach(function(changeSet) {
77-
// | ----[match]------
78-
var slicedText = text.slice(changeSet.index);
79-
// | ----[match------|
80-
var matchedText = slicedText.slice(0, changeSet.matches[0].length);
81-
var expected = matchedText.replace(changeSet.pattern, changeSet.expected);
82-
// Avoid accidental match(ignore case)
83-
if (matchedText === expected) {
114+
const makeChangeSet = prhEngine.makeChangeSet(null, text);
115+
forEachChange(makeChangeSet, text, ({matchStartIndex, matchEndIndex, actual, expected}) => {
116+
// If result is not changed, should not report
117+
if (actual === expected) {
84118
return;
85119
}
86-
/*
87-
line start with 1
88-
column start with 0
89-
90-
adjust position => line -1, column +0
91-
*/
92-
var position = src.indexToPosition(changeSet.index);
93-
94-
// line, column
95-
report(node, new RuleError(matchedText + " => " + expected, {
96-
line: position.line - 1,
97-
column: position.column,
98-
fix: fixer.replaceTextRange([changeSet.index, changeSet.index + matchedText.length], expected)
120+
report(node, new RuleError(actual + " => " + expected, {
121+
index: matchStartIndex,
122+
fix: fixer.replaceTextRange([matchStartIndex, matchEndIndex], expected)
99123
}));
100124
});
101125
}

test/fixtures/example-prh.yml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
version: 1
2+
3+
rules:
4+
5+
# 大文字小文字全角半角の統一
6+
- expected: Cookie
7+
# 以下と等価 正規表現には強制でgフラグが付く
8+
# - expected: Cookie
9+
# pattern: "/[CcCc][OoOo][OoOo][KkKk][IiIi][EeEe]/g"
10+
# options:
11+
# wordBoundary: false
12+
# specs: []
13+
14+
# 変換結果についてテストも書ける
15+
- expected: jQuery
16+
specs:
17+
- from: jquery
18+
to: jQuery
19+
- from: JQUERY
20+
to: jQuery
21+
22+
# 変換結果が期待通りではなかった場合、ルールのロードに失敗する つまり、ルールのテストが書ける
23+
# - expected: JavaScript
24+
# specs:
25+
# - from: JAVASCRIPT
26+
# to: JavaScprit # この場合はテスト側が間違ってる!
27+
# Error: JavaScript spec failed. "JAVASCRIPT", expected "JavaScprit", but got "JavaScript", /[JjJj][AaAa][VvVv][AaAa][SsSs][CcCc][RrRr][IiIi][PpPp][TtTt]/g
28+
29+
# 表現の統一を図る
30+
- expected: デフォルト
31+
pattern: ディフォルト
32+
33+
# patternは複数記述可能 patterns としてもOK
34+
- expected: ハードウェア
35+
patterns:
36+
- ハードウエアー # 正規表現に変換する都合上、より長いものを先に書いたほうがよい
37+
- ハードウェアー
38+
- ハードウエア
39+
40+
# patternには正規表現が利用可能
41+
- expected: ($1)
42+
pattern: /\(([^)]+)\)/
43+
specs:
44+
# 半角括弧を全角括弧へ
45+
- from: (そのとおり)
46+
to: (そのとおり)
47+
48+
# 否定戻り先読みが欲しいがJSにはない… regexpMustEmptyで、特定のキャプチャグループが空であることを指定して代用とする
49+
- expected: ソフトウェア
50+
pattern: /(日経)?ソフトウエア/
51+
regexpMustEmpty: $1
52+
specs:
53+
# 普通に変換
54+
- from: 広義のソフトウエア
55+
to: 広義のソフトウェア
56+
# 日経ソフトウエア(書名)は変換しない
57+
- from: 日経ソフトウエア
58+
to: 日経ソフトウエア
59+
60+
# 長音の統一には否定後読みを活用する そうしないと サーバー が サーバーー にされてしまったりする
61+
- expected: サーバー
62+
pattern: /サーバ(?!ー)/
63+
specs:
64+
- from: サーバ
65+
to: サーバー
66+
67+
# 単語境界の区別
68+
- expected: js
69+
# pattern: "/\b[JjJj][SsSs]\b/g" # と等価 \b が前後に付与される
70+
options:
71+
wordBoundary: true
72+
specs:
73+
- from: foo JS bar
74+
to: foo js bar
75+
- from: foo altJS bar
76+
to: foo altJS bar
77+
# 日本語+単語境界の仕様は自分で調べてね…!
78+
- from: 今日もJS祭り
79+
to: 今日もjs祭り

test/fixtures/prefer-regexp.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
version: 1
2+
3+
rules:
4+
- prh: 「行う」「行なう」は開く。
5+
expected: おこな
6+
pattern: /(おこな|行な?)(?=[わいっうえお])/
7+
specs:
8+
- from: 行わない
9+
to: おこなわない
10+
- from: 行います
11+
to: おこないます
12+
- from: 行うとき
13+
to: おこなうとき
14+
- from: 行えば
15+
to: おこなえば
16+
- from: 行なう
17+
to: おこなう
18+
- from: おこなう
19+
to: おこなう
20+
# チェックされなくてよい部分
21+
- from: 行く
22+
to: 行く

test/prh-rule-tester-test.js

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,185 @@ tester.run("prh", rule, {
3737
column: 1
3838
}
3939
]
40+
},
41+
{
42+
text: "行う、行なう、おこなう。行って、行わない、行ないます。",
43+
output: "おこなう、おこなう、おこなう。おこなって、おこなわない、おこないます。",
44+
options: {
45+
"rulePaths": [__dirname + "/fixtures/prefer-regexp.yml"]
46+
},
47+
errors: [
48+
{
49+
"type": "lint",
50+
"ruleId": "prh",
51+
"message": "行 => おこな",
52+
"index": 0,
53+
"line": 1,
54+
"column": 1,
55+
"severity": 2,
56+
"fix": {
57+
"range": [
58+
0,
59+
1
60+
],
61+
"text": "おこな"
62+
}
63+
},
64+
{
65+
"type": "lint",
66+
"ruleId": "prh",
67+
"message": "行な => おこな",
68+
"index": 3,
69+
"line": 1,
70+
"column": 4,
71+
"severity": 2,
72+
"fix": {
73+
"range": [
74+
3,
75+
5
76+
],
77+
"text": "おこな"
78+
}
79+
},
80+
{
81+
"type": "lint",
82+
"ruleId": "prh",
83+
"message": "行 => おこな",
84+
"index": 12,
85+
"line": 1,
86+
"column": 13,
87+
"severity": 2,
88+
"fix": {
89+
"range": [
90+
12,
91+
13
92+
],
93+
"text": "おこな"
94+
}
95+
},
96+
{
97+
"type": "lint",
98+
"ruleId": "prh",
99+
"message": "行 => おこな",
100+
"index": 16,
101+
"line": 1,
102+
"column": 17,
103+
"severity": 2,
104+
"fix": {
105+
"range": [
106+
16,
107+
17
108+
],
109+
"text": "おこな"
110+
}
111+
},
112+
{
113+
"type": "lint",
114+
"ruleId": "prh",
115+
"message": "行な => おこな",
116+
"index": 21,
117+
"line": 1,
118+
"column": 22,
119+
"severity": 2,
120+
"fix": {
121+
"range": [
122+
21,
123+
23
124+
],
125+
"text": "おこな"
126+
}
127+
}
128+
]
129+
},
130+
// example-prh.yml
131+
{
132+
text: "jqueryではクッキー。ディフォルトとハードウエアー。(そのとおり)\nサーバはサーバーサイドをjsする。",
133+
output: "jQueryではクッキー。デフォルトとハードウェア。(そのとおり)\nサーバーはサーバーサイドをjsする。",
134+
options: {
135+
"rulePaths": [__dirname + "/fixtures/example-prh.yml"]
136+
},
137+
errors: [
138+
{
139+
"type": "lint",
140+
"ruleId": "prh",
141+
"message": "jquery => jQuery",
142+
"index": 0,
143+
"line": 1,
144+
"column": 1,
145+
"severity": 2,
146+
"fix": {
147+
"range": [
148+
0,
149+
6
150+
],
151+
"text": "jQuery"
152+
}
153+
},
154+
{
155+
"type": "lint",
156+
"ruleId": "prh",
157+
"message": "ディフォルト => デフォルト",
158+
"index": 13,
159+
"line": 1,
160+
"column": 14,
161+
"severity": 2,
162+
"fix": {
163+
"range": [
164+
13,
165+
19
166+
],
167+
"text": "デフォルト"
168+
}
169+
},
170+
{
171+
"type": "lint",
172+
"ruleId": "prh",
173+
"message": "ハードウエアー => ハードウェア",
174+
"index": 20,
175+
"line": 1,
176+
"column": 21,
177+
"severity": 2,
178+
"fix": {
179+
"range": [
180+
20,
181+
27
182+
],
183+
"text": "ハードウェア"
184+
}
185+
},
186+
{
187+
"type": "lint",
188+
"ruleId": "prh",
189+
"message": "(そのとおり) => (そのとおり)",
190+
"index": 28,
191+
"line": 1,
192+
"column": 29,
193+
"severity": 2,
194+
"fix": {
195+
"range": [
196+
28,
197+
35
198+
],
199+
"text": "(そのとおり)"
200+
}
201+
},
202+
{
203+
"type": "lint",
204+
"ruleId": "prh",
205+
"message": "サーバ => サーバー",
206+
"index": 36,
207+
"line": 2,
208+
"column": 1,
209+
"severity": 2,
210+
"fix": {
211+
"range": [
212+
36,
213+
39
214+
],
215+
"text": "サーバー"
216+
}
217+
}
218+
]
40219
}
41220
]
42221
});

0 commit comments

Comments
 (0)