Skip to content

Commit

Permalink
fix(rule): support skipNodeTypes and fix list bug
Browse files Browse the repository at this point in the history
fix #1
fix #5
  • Loading branch information
azu committed Jan 3, 2019
1 parent 4840bcb commit 5a238cb
Show file tree
Hide file tree
Showing 5 changed files with 751 additions and 76 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
"kuromojin": "^1.3.2",
"morpheme-match": "^1.2.1",
"morpheme-match-all": "^1.2.0",
"textlint-rule-helper": "^2.1.0"
"textlint-rule-helper": "^2.1.1",
"textlint-util-to-string": "^2.1.1"
},
"keywords": [
"textlintrule"
Expand All @@ -61,4 +62,4 @@
"git add"
]
}
}
}
128 changes: 75 additions & 53 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// MIT © 2016 azu
"use strict";
import { wrapReportHandler } from "textlint-rule-helper";
import StringSource from "textlint-util-to-string";

const tokenize = require("kuromojin").tokenize;
const dictionaryList = require("./dictionary");
const createMatchAll = require("morpheme-match-all");
Expand Down Expand Up @@ -48,61 +51,80 @@ const createMessage = ({ text, matcherTokens, skipped, actualTokens }) => {
return resultText;
};

const reporter = context => {
const { Syntax, RuleError, report, fixer, getSource } = context;
const reporter = (context, options = {}) => {
const { Syntax, RuleError, fixer } = context;
const DefaultOptions = {
// https://textlint.github.io/docs/txtnode.html#type
skipNodeTypes: [Syntax.BlockQuote, Syntax.Link, Syntax.ReferenceDef]
};
const matchAll = createMatchAll(dictionaryList);
return {
[Syntax.Str](node) {
const text = getSource(node);
return tokenize(text).then(currentTokens => {
/**
* @type {MatchResult[]}
*/
const matchResults = matchAll(currentTokens);
matchResults.forEach(matchResult => {
const firstToken = matchResult.tokens[0];
const lastToken = matchResult.tokens[matchResult.tokens.length - 1];
const firstWordIndex = Math.max(firstToken.word_position - 1, 0);
const lastWorkIndex = Math.max(lastToken.word_position - 1, 0);
// replace $1
const message =
createMessage({
text: matchResult.dict.message,
matcherTokens: matchResult.dict.tokens,
skipped: matchResult.skipped,
actualTokens: matchResult.tokens
}) + (matchResult.dict.url ? `参考: ${matchResult.dict.url}` : "");
const expected = matchResult.dict.expected
? createExpected({
text: matchResult.dict.expected,
matcherTokens: matchResult.dict.tokens,
skipped: matchResult.skipped,
actualTokens: matchResult.tokens
})
: undefined;
if (expected) {
report(
node,
new RuleError(message, {
index: firstWordIndex,
fix: fixer.replaceTextRange(
[firstWordIndex, lastWorkIndex + lastToken.surface_form.length],
expected
)
})
);
} else {
report(
node,
new RuleError(message, {
index: firstWordIndex
})
);
}
});
});
const skipNodeTypes = options.skipNodeTypes || DefaultOptions.skipNodeTypes;
return wrapReportHandler(
context,
{
ignoreNodeTypes: skipNodeTypes
},
report => {
return {
[Syntax.Paragraph](node) {
const source = new StringSource(node);
const text = source.toString();
return tokenize(text).then(currentTokens => {
/**
* @type {MatchResult[]}
*/
const matchResults = matchAll(currentTokens);
matchResults.forEach(matchResult => {
const firstToken = matchResult.tokens[0];
const lastToken = matchResult.tokens[matchResult.tokens.length - 1];
const firstWordIndex = source.originalIndexFromIndex(
Math.max(firstToken.word_position - 1, 0)
);
const lastWordIndex = source.originalIndexFromIndex(
Math.max(lastToken.word_position - 1, 0)
);
// replace $1
const message =
createMessage({
text: matchResult.dict.message,
matcherTokens: matchResult.dict.tokens,
skipped: matchResult.skipped,
actualTokens: matchResult.tokens
}) + (matchResult.dict.url ? `参考: ${matchResult.dict.url}` : "");
const expected = matchResult.dict.expected
? createExpected({
text: matchResult.dict.expected,
matcherTokens: matchResult.dict.tokens,
skipped: matchResult.skipped,
actualTokens: matchResult.tokens
})
: undefined;
if (expected) {
const wordLength = lastToken.surface_form.length;
report(
node,
new RuleError(message, {
index: firstWordIndex,
fix: fixer.replaceTextRange(
[firstWordIndex, lastWordIndex + wordLength],
expected
)
})
);
} else {
report(
node,
new RuleError(message, {
index: firstWordIndex
})
);
}
});
});
}
};
}
};
);
};
module.exports = {
linter: reporter,
Expand Down
34 changes: 33 additions & 1 deletion test/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,41 @@ tester.run("textlint-rule-ja-no-redundant-expression", rule, {
"text",
// することができるが正当な文 - http://qiita.com/takahi-i/items/a93dc2ff42af6b93f6e0#comment-850ec4d194748453a39a
"人は1人では育つことができない",
"あいつにだけは絶対抜かれることはできない"
"あいつにだけは絶対抜かれることはできない",
`> これは省略することが可能だが、省略しない。`, // 引用文はOK
`[これは省略することが可能](http://example)だが、省略しない。`, // リンクはOK
`
1. 省略する
2. ことが可能
3. リストはアイテムごとにチェックする
`
],
invalid: [
// option
{
text: "> これは省略することが可能だが、省略しない。",
options: {
// すべてをチェックする
skipNodeTypes: []
},
errors: [
{
message: `"することが可能だ"は冗長な表現です。"することが可能"を省き簡潔な表現にすると文章が明瞭になります。参考: http://qiita.com/takahi-i/items/a93dc2ff42af6b93f6e0`,
index: 7
}
]
},
// code + str
{
text: "`code`は省略することが可能だが、省略しない。",
errors: [
{
message: `"することが可能だ"は冗長な表現です。"することが可能"を省き簡潔な表現にすると文章が明瞭になります。参考: http://qiita.com/takahi-i/items/a93dc2ff42af6b93f6e0`,
index: 9
}
]
},
// check
{
text: "これは省略することが可能だが、省略しない。",
errors: [
Expand Down
2 changes: 2 additions & 0 deletions test/mocha.opts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--require textlint-scripts/register
--timeout 10000
Loading

0 comments on commit 5a238cb

Please sign in to comment.