Skip to content

Commit

Permalink
feat(space-between-half-and-full-width): add an option to ignore numb…
Browse files Browse the repository at this point in the history
…ers and apply only to alphabets (#45)

## 変更点

- `space`オプションにスペースを入れる対象の配列を指定できるように
  - `"space": ["alphabets", "numbers", "punctuation"]` でアルファベット、数値、句読点の前後にスペースを入れる(`"space": "always"`と同じ意味)
  - `"space": ["alphabets", "punctuation"]` とすると アルファベットと句読点の前後にスペースを入れる
  - `"space": []` で全てにスペースを入れない(`"space": "never"`と同じ意味)
- `exceptPunctuation` オプションは `"space": ["alphabets", "numbers"]`で代用できるため、非推奨となりました
  • Loading branch information
Sean0628 authored Jan 23, 2023
1 parent 4fb91b2 commit 4eec7ed
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
OK: これはUnicode
NG: これは Unicode

全角文字には、句読点(、。)も含まれていますがデフォルトでは、`exceptPunctuation: true`であるため無視されます
全角文字には、句読点(、。)も含まれていますがデフォルトでは、有効であるため無視されます

OK: これも、Unicode。

Expand Down Expand Up @@ -42,16 +42,21 @@ textlint --rule ja-space-between-half-and-full-width README.md

## Options

- `space`: `"always"` || `"never"`
- `space`: `"always"` || `"never"` || `string[]`
- デフォルト: `"never"`
- スペースを常に 入れる(`"always"`) or 入れない(`"never"`)
- `exceptPunctuation`: `boolean`
- Array 形式での指定も可能: `["alphabets", "numbers", "punctuation"]`
- 対象としたい物のみ指定する
- 例えば、数値と句読点(、。)を例外として扱いたい場合は以下
- `["alphabets"]`
- (非推奨)`exceptPunctuation`: `boolean`
- デフォルト: `true`
- 句読点(、。)を例外として扱うかどうか
- 代わりに `space` オプションを用いて `["alphabets", "numbers"]` と指定する
- `lintStyledNode`: `boolean`
- デフォルト: `false`
- プレーンテキスト以外(リンクや画像のキャプションなど)を lint の対象とするかどうか (プレーンテキストの判断基準は [textlint/textlint-rule-helper: This is helper library for creating textlint rule](https://github.com/textlint/textlint-rule-helper#rulehelperisplainstrnodenode-boolean) を参照してください)

```json
{
"rules": {
Expand All @@ -60,26 +65,24 @@ textlint --rule ja-space-between-half-and-full-width README.md
}
}
}
```
```

`exceptPunctuation: true`とした場合は、句読点に関しては無視されるようになります。
`space` オプションに `"punctuation"` を含めない場合は、句読点に関しては無視されるようになります。

スペースは必須だが、`日本語、[alphabet]。`は許可する
スペースは必須だが、`日本語、[alphabet]。`は許可する

text: "これは、Exception。",
options: {
space: "always",
exceptPunctuation: true
space: ["alphabets", "numbers"]
}

スペースは不要だが、`日本語、 [alphabet] 。`は許可する。

text: "これは、 Exception 。",
options: {
space: "never",
exceptPunctuation: true
space: []
}


## Changelog

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,58 @@ const assert = require("assert");
import {RuleHelper} from "textlint-rule-helper";
import {matchCaptureGroupAll} from "match-index";
const PunctuationRegExp = /[。、]/;
const ZenRegExpStr = '[、。]|[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[ぁ-んァ-ヶ]';
const defaultSpaceOptions = {
alphabets: false,
numbers: false,
punctuation: false
};
const defaultOptions = {
// スペースを入れるかどうか
// "never" or "always"
space: "never",
// [。、,.]を例外とするかどうか
exceptPunctuation: true,
// プレーンテキスト以外を対象とするか See https://github.com/textlint/textlint-rule-helper#rulehelperisplainstrnodenode-boolean
lintStyledNode: false,
};
function reporter(context, options = {}) {
/**
* 入力された `space` オプションを内部処理用に成形する
* @param {string|Array|undefined} opt `space` オプションのインプット
* @param {boolean|undefined} exceptPunctuation `exceptPunctuation` オプションのインプット
* @returns {Object}
*/
const parseSpaceOption = (opt, exceptPunctuation) => {
if (typeof opt === 'string') {
assert(opt === "always" || opt === "never", `"space" options should be "always", "never" or an array.`);

if (opt === "always") {
if (exceptPunctuation === false) {
return {...defaultSpaceOptions, alphabets: true, numbers: true, punctuation: true};
} else {
return {...defaultSpaceOptions, alphabets: true, numbers: true};
}
} else if (opt === "never") {
if (exceptPunctuation === false) {
return {...defaultSpaceOptions, punctuation: true};
} else {
return defaultSpaceOptions;
}
}
} else if (Array.isArray(opt)) {
assert(
opt.every((v) => Object.keys(defaultSpaceOptions).includes(v)),
`Only "alphabets", "numbers", "punctuation" can be included in the array.`
);
const userOptions = Object.fromEntries(opt.map(key => [key, true]));
return {...defaultSpaceOptions, ...userOptions};
}

return defaultSpaceOptions;
}

const {Syntax, RuleError, report, fixer, getSource} = context;
const helper = new RuleHelper();
const spaceOption = options.space || defaultOptions.space;
const exceptPunctuation = options.exceptPunctuation !== undefined
? options.exceptPunctuation
: defaultOptions.exceptPunctuation;
const spaceOption = parseSpaceOption(options.space, options.exceptPunctuation);
const lintStyledNode = options.lintStyledNode !== undefined
? options.lintStyledNode
: defaultOptions.lintStyledNode;
assert(spaceOption === "always" || spaceOption === "never", `"space" options should be "always" or "never".`);
/**
* `text`を対象に例外オプションを取り除くfilter関数を返す
* @param {string} text テスト対象のテキスト全体
Expand All @@ -35,7 +67,7 @@ function reporter(context, options = {}) {
*/
const createFilter = (text, padding) => {
/**
* `exceptPunctuation`で指定された例外を取り除く
* `PunctuationRegExp`で指定された例外を取り除く
* @param {Object} match
* @returns {boolean}
*/
Expand All @@ -44,16 +76,16 @@ function reporter(context, options = {}) {
if (!targetChar) {
return false;
}
if (exceptPunctuation && PunctuationRegExp.test(targetChar)) {
if (!spaceOption.punctuation && PunctuationRegExp.test(targetChar)) {
return false;
}
return true;
}
};
// Never: アルファベットと全角の間はスペースを入れない
const noSpaceBetween = (node, text) => {
const betweenHanAndZen = matchCaptureGroupAll(text, /[A-Za-z0-9]([  ])(?:[、。]|[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[ぁ-んァ-ヶ])/);
const betweenZenAndHan = matchCaptureGroupAll(text, /(?:[、。]|[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[ぁ-んァ-ヶ])([  ])[A-Za-z0-9]/);
const betweenHanAndZen = matchCaptureGroupAll(text, new RegExp(`[A-Za-z0-9]([  ])(?:${ZenRegExpStr})`));
const betweenZenAndHan = matchCaptureGroupAll(text, new RegExp(`(?:${ZenRegExpStr})([  ])[A-Za-z0-9]`));
const reportMatch = (match) => {
const {index} = match;
report(node, new RuleError("原則として、全角文字と半角文字の間にスペースを入れません。", {
Expand All @@ -66,12 +98,36 @@ function reporter(context, options = {}) {
};

// Always: アルファベットと全角の間はスペースを入れる
const needSpaceBetween = (node, text) => {
const betweenHanAndZen = matchCaptureGroupAll(text, /([A-Za-z0-9])(?:[、。]|[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[ぁ-んァ-ヶ])/);
const betweenZenAndHan = matchCaptureGroupAll(text, /([、。]|[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF]|[ぁ-んァ-ヶ])[A-Za-z0-9]/);
const needSpaceBetween = (node, text, options) => {
/**
* オプションを元に正規表現オプジェクトを生成する
* @param {Array} opt `space` オプション
* @param {boolean} btwHanAndZen=true 半角全角の間か全角半角の間か
* @returns {Object}
*/
const generateRegExp = (opt, btwHanAndZen = true) => {
const alphabets = opt.alphabets ? 'A-Za-z' : '';
const numbers = opt.numbers ? '0-9' : '';

let expStr;
if (btwHanAndZen) {
expStr = `([${alphabets}${numbers}])(?:${ZenRegExpStr})`;
} else {
expStr = `(${ZenRegExpStr})[${alphabets}${numbers}]`;
}

return new RegExp(expStr);
};

const betweenHanAndZenRegExp = generateRegExp(options);
const betweenZenAndHanRegExp = generateRegExp(options, false);
const errorMsg = '原則として、全角文字と半角文字の間にスペースを入れます。';

const betweenHanAndZen = matchCaptureGroupAll(text, betweenHanAndZenRegExp);
const betweenZenAndHan = matchCaptureGroupAll(text, betweenZenAndHanRegExp);
const reportMatch = (match) => {
const {index} = match;
report(node, new RuleError("原則として、全角文字と半角文字の間にスペースを入れます。", {
report(node, new RuleError(errorMsg, {
index: match.index,
fix: fixer.replaceTextRange([index + 1, index + 1], " ")
}));
Expand All @@ -86,12 +142,12 @@ function reporter(context, options = {}) {
}
const text = getSource(node);

if (spaceOption === "always") {
needSpaceBetween(node, text)
} else if (spaceOption === "never") {
const noSpace = (key) => key === 'punctuation' ? true : !spaceOption[key];
if (Object.keys(spaceOption).every(noSpace)) {
noSpaceBetween(node, text);
} else {
needSpaceBetween(node, text, spaceOption);
}

}
}
}
Expand Down
Loading

0 comments on commit 4eec7ed

Please sign in to comment.