@textlint-ja/textlint-rule-preset-ai-writing
Version:
textlintプリセット:AIっぽい記述パターンを検出し、より自然な日本語表現を促すルール集
154 lines (141 loc) • 7.05 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _regexpStringMatcher = require("@textlint/regexp-string-matcher");
const rule = (context, options = {}) => {
const {
Syntax,
RuleError,
report,
getSource,
locator,
fixer
} = context;
const allows = options.allows ?? [];
const disableEmojiEmphasisPatterns = options.disableEmojiEmphasisPatterns ?? false;
const disableInfoPatterns = options.disableInfoPatterns ?? false;
const disableHeadingEmphasisPatterns = options.disableHeadingEmphasisPatterns ?? false;
// 機械的な情報系プレフィックス
const infoPatterns = ["注意", "重要", "ポイント", "メモ", "参考", "補足", "確認", "チェック", "推奨", "おすすめ", "検出される例", "推奨される表現", "良い例", "悪い例", "例", "サンプル", "使用例", "設定例"];
return {
[Syntax.Paragraph](node) {
const text = getSource(node);
// allowsパターンにマッチする場合はスキップ
if (allows.length > 0) {
const matchedResults = (0, _regexpStringMatcher.matchPatterns)(text, allows);
if (matchedResults.length > 0) {
return;
}
}
// リストアイテムの場合はListItemのハンドラーで処理するのでスキップ
if (node.parent?.type === "ListItem") {
return;
}
const emojiEmphasizeMatches = [];
// 絵文字 + 太字の組み合わせパターンを検出
if (!disableEmojiEmphasisPatterns) {
// 絵文字の正規表現を修正(Unicodeフラグでサロゲートペア対応)
const emojiEmphasizePattern = /(ℹ️|🔍|✅|❌|⚠️|💡|📝|📋|📌|🔗|🎯|🚀|⭐|✨|💯|🔥|📊|📈)\s*\*\*([^*]+)\*\*/gu;
for (const match of text.matchAll(emojiEmphasizePattern)) {
const matchStart = match.index ?? 0;
const matchEnd = matchStart + match[0].length;
emojiEmphasizeMatches.push(match);
report(node, new RuleError(`絵文字と太字の組み合わせは機械的な印象を与える可能性があります。より自然な表現を検討してください。`, {
padding: locator.range([matchStart, matchEnd])
}));
}
}
// 情報系プレフィックスの太字パターンを検出(絵文字+太字と重複しない場合のみ)
if (!disableInfoPatterns) {
const infoPrefixPattern = new RegExp(`\\*\\*(${infoPatterns.join("|")})([::].*?)?\\*\\*`, "g");
for (const match of text.matchAll(infoPrefixPattern)) {
const matchStart = match.index ?? 0;
const matchEnd = matchStart + match[0].length;
const prefixText = match[1];
// 絵文字+太字のマッチと重複していないかチェック
const isOverlapping = emojiEmphasizeMatches.some(emojiMatch => {
const emojiStart = emojiMatch.index ?? 0;
const emojiEnd = emojiStart + emojiMatch[0].length;
return matchStart < emojiEnd && matchEnd > emojiStart;
});
if (!isOverlapping) {
report(node, new RuleError(`「**${prefixText}**」のような太字の情報プレフィックスは機械的な印象を与える可能性があります。より自然な表現を検討してください。`, {
padding: locator.range([matchStart, matchEnd])
}));
}
}
}
},
[Syntax.ListItem](node) {
const text = getSource(node);
// allowsパターンにマッチする場合はスキップ
if (allows.length > 0) {
const matchedResults = (0, _regexpStringMatcher.matchPatterns)(text, allows);
if (matchedResults.length > 0) {
return;
}
}
const emojiEmphasizeMatches = [];
// リストアイテム内での絵文字 + 太字パターンを検出
if (!disableEmojiEmphasisPatterns) {
const emojiEmphasizePattern = /(ℹ️|🔍|✅|❌|⚠️|💡|📝|📋|📌|🔗|🎯|🚀|⭐|✨|💯|🔥|📊|📈)\s*\*\*([^*]+)\*\*/gu;
for (const match of text.matchAll(emojiEmphasizePattern)) {
const matchStart = match.index ?? 0;
const matchEnd = matchStart + match[0].length;
emojiEmphasizeMatches.push(match);
report(node, new RuleError(`リストアイテムで絵文字と太字の組み合わせは機械的な印象を与える可能性があります。より自然な表現を検討してください。`, {
padding: locator.range([matchStart, matchEnd])
}));
}
}
// リストアイテム内での情報系プレフィックスの太字パターンを検出
if (!disableInfoPatterns) {
const infoPrefixPattern = new RegExp(`\\*\\*(${infoPatterns.join("|")})([::].*?)?\\*\\*`, "g");
for (const match of text.matchAll(infoPrefixPattern)) {
const matchStart = match.index ?? 0;
const matchEnd = matchStart + match[0].length;
const prefixText = match[1];
// 絵文字+太字のマッチと重複していないかチェック
const isOverlapping = emojiEmphasizeMatches.some(emojiMatch => {
const emojiStart = emojiMatch.index ?? 0;
const emojiEnd = emojiStart + emojiMatch[0].length;
return matchStart < emojiEnd && matchEnd > emojiStart;
});
if (!isOverlapping) {
report(node, new RuleError(`リストアイテムで「**${prefixText}**」のような太字の情報プレフィックスは機械的な印象を与える可能性があります。より自然な表現を検討してください。`, {
padding: locator.range([matchStart, matchEnd])
}));
}
}
}
},
[Syntax.Header](node) {
if (disableHeadingEmphasisPatterns) {
return;
}
const text = getSource(node);
// allowsパターンにマッチする場合はスキップ
if (allows.length > 0) {
const matchedResults = (0, _regexpStringMatcher.matchPatterns)(text, allows);
if (matchedResults.length > 0) {
return;
}
}
// 見出し内の太字パターンを検出(**text** または __text__)
const headingEmphasisPattern = /(\*\*|__)(.*?)\1/g;
for (const match of text.matchAll(headingEmphasisPattern)) {
const matchStart = match.index ?? 0;
const matchEnd = matchStart + match[0].length;
const innerText = match[2];
report(node, new RuleError(`見出し内の太字は不要です。見出し自体が強調のため、追加の太字は冗長です。`, {
padding: locator.range([matchStart, matchEnd]),
fix: fixer.replaceTextRange([matchStart, matchEnd], innerText)
}));
}
}
};
};
var _default = exports.default = rule;
//# sourceMappingURL=no-ai-emphasis-patterns.js.map