-
Notifications
You must be signed in to change notification settings - Fork 21
feat: 見出し冒頭で連番を使うパターンの検知 #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import { matchPatterns } from "@textlint/regexp-string-matcher"; | ||
| import type { TextlintRuleContext, TextlintRuleModule } from "@textlint/types"; | ||
|
|
||
| type Options = { | ||
| // If node's text includes allowed patterns, does not report. | ||
| // Can be string or RegExp-like string ("/pattern/flags") | ||
| allows?: string[]; | ||
| }; | ||
|
|
||
| const rule: TextlintRuleModule<Options> = (context: TextlintRuleContext, options = {}) => { | ||
| const { Syntax, RuleError, report, getSource, locator } = context; | ||
| const allows = options.allows ?? []; | ||
|
|
||
| // Pattern to detect numbering at the beginning of headings | ||
| // Matches patterns like: "1. ", "2. ", "3.1. ", "1) ", etc. | ||
| const numberingPattern = /^\s*(\d+(?:\.\d+)*[.)]\s+)/; | ||
|
|
||
| return { | ||
| [Syntax.Header](node) { | ||
| // Use raw text which includes the heading marker (#) | ||
| const text = node.raw || getSource(node); | ||
|
|
||
| // Check if text matches any allowed patterns | ||
| if (allows.length > 0) { | ||
| const matches = matchPatterns(text, allows); | ||
| if (matches.length > 0) { | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| // For raw text, we need to skip the heading marker first | ||
| // Extract just the heading content (after # and space) | ||
| const headingContentMatch = text.match(/^#{1,6}\s+(.+)$/); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ここはtextをtrim or |
||
| const headingContent = headingContentMatch ? headingContentMatch[1] : text; | ||
|
|
||
| const match = headingContent.match(numberingPattern); | ||
| if (match) { | ||
| const numberPart = match[1]; | ||
| const matchStart = match.index ?? 0; | ||
| const matchEnd = matchStart + numberPart.length; | ||
|
|
||
| report( | ||
| node, | ||
| new RuleError( | ||
| "見出しに連番を含めるパターンは機械的な印象を与える可能性があります。連番の必要性を検討してみてください。", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 具体的には、見出しのレベルを見直することで、見出しを番号として含める必要がないので、見出しのレベルを1や2のようなテキストで表現しないでください ということが伝わると何をするといいかわかるかもしれないです。 HTML的にはこの辺だと思いますが、ここまで詳細にはいらないですが、 まだちょっと手つけられてないですが、エラーメッセージの明確性/正確性/修正可能性/文脈適合性を評価して、人間もAIもエラーを直しやすくできるような基準を作ろうとしています。 この場合、どうやって修正したらいいかがちょっと伝わりにくいかなと思いました |
||
| { | ||
| padding: locator.range([matchStart, matchEnd]) | ||
| } | ||
| ) | ||
| ); | ||
| } | ||
| } | ||
| }; | ||
| }; | ||
|
|
||
| export default rule; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| import TextLintTester from "textlint-tester"; | ||
| import noAiHeadingNumbers from "../../src/rules/no-ai-heading-numbers"; | ||
|
|
||
| const tester = new TextLintTester(); | ||
|
|
||
| tester.run("no-ai-heading-numbers", noAiHeadingNumbers, { | ||
| valid: [ | ||
| // Normal headings without numbering | ||
| "# タイトル", | ||
| "## セクション", | ||
| "### サブセクション", | ||
| "#### 詳細項目", | ||
| "##### 更に詳細な項目", | ||
| "###### 最小レベルの見出し", | ||
| // Headings with text that happens to start with numbers (but no dot/paren) | ||
| "# 2025年の振り返り", | ||
| "## 3つのポイント", | ||
| // Allowed patterns (string) | ||
| { | ||
| text: "# 1. イントロダクション", | ||
| options: { | ||
| allows: ["1. イントロダクション"] | ||
| } | ||
| }, | ||
| // Allowed patterns (RegExp-like string) | ||
| { | ||
| text: "## 2. 背景", | ||
| options: { | ||
| allows: ["/\\d+\\. .*/"] | ||
| } | ||
| }, | ||
| // Allowed patterns (case insensitive) | ||
| { | ||
| text: "### 3.1. サブセクション", | ||
| options: { | ||
| allows: ["/\\d+\\.\\d+\\. .*/i"] | ||
| } | ||
| } | ||
| ], | ||
| invalid: [ | ||
| // Simple numbering patterns | ||
| { | ||
| text: "# 1. タイトル", | ||
| errors: [ | ||
| { | ||
| message: | ||
| "見出しに連番を含めるパターンは機械的な印象を与える可能性があります。連番の必要性を検討してみてください。", | ||
| range: [0, 3] | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| text: "## 2. セクション", | ||
| errors: [ | ||
| { | ||
| message: | ||
| "見出しに連番を含めるパターンは機械的な印象を与える可能性があります。連番の必要性を検討してみてください。", | ||
| range: [0, 3] | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| text: "### 10. 10番目のアイテム", | ||
| errors: [ | ||
| { | ||
| message: | ||
| "見出しに連番を含めるパターンは機械的な印象を与える可能性があります。連番の必要性を検討してみてください。", | ||
| range: [0, 4] | ||
| } | ||
| ] | ||
| }, | ||
| // Hierarchical numbering (1.1, 1.2, etc.) | ||
| { | ||
| text: "### 3.1. サブセクション", | ||
| errors: [ | ||
| { | ||
| message: | ||
| "見出しに連番を含めるパターンは機械的な印象を与える可能性があります。連番の必要性を検討してみてください。", | ||
| range: [0, 5] | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| text: "#### 1.2.3. 深い階層", | ||
| errors: [ | ||
| { | ||
| message: | ||
| "見出しに連番を含めるパターンは機械的な印象を与える可能性があります。連番の必要性を検討してみてください。", | ||
| range: [0, 7] | ||
| } | ||
| ] | ||
| }, | ||
| // Parenthesis style numbering | ||
| { | ||
| text: "# 1) はじめに", | ||
| errors: [ | ||
| { | ||
| message: | ||
| "見出しに連番を含めるパターンは機械的な印象を与える可能性があります。連番の必要性を検討してみてください。", | ||
| range: [0, 3] | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| text: "## 2) 背景", | ||
| errors: [ | ||
| { | ||
| message: | ||
| "見出しに連番を含めるパターンは機械的な印象を与える可能性があります。連番の必要性を検討してみてください。", | ||
| range: [0, 3] | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Header nodeの時点でHeaderなのでrawを見る必要はないと思います。
具体的な問題として
#を使わない書き方だと動かなくなる(====やMarkdown以外など)ので、getSourceだけで良いような気がします。AIは
====の方はあんまり使わない感じはするので、意図的に除外する場合は意図的な動作を実装したいですね