@@ -186,90 +186,173 @@ const rule: TextlintRuleModule<Options> = (context, options = {}) => {
186186 structure : 0
187187 } ;
188188
189- return {
190- [ Syntax . Paragraph ] ( node ) {
191- // StringSourceを使用してコードブロックを除外したテキストを取得
192- const source = new StringSource ( node , {
193- replacer ( { node, emptyValue } ) {
194- // コードブロック、インラインコードを除外
195- if ( node . type === "Code" || node . type === "InlineCode" ) {
196- return emptyValue ( ) ;
189+ /**
190+ * コロン(:)で終わる段落の直後に箇条書きが続くパターンを検出
191+ */
192+ const detectColonListPattern = ( node : any ) => {
193+ const children = node . children || [ ] ;
194+
195+ for ( let i = 0 ; i < children . length - 1 ; i ++ ) {
196+ const currentNode = children [ i ] ;
197+ const nextNode = children [ i + 1 ] ;
198+
199+ // Paragraph → List のパターンを検出
200+ if ( currentNode . type === "Paragraph" && nextNode . type === "List" ) {
201+ // Paragraphの最後の文字列ノードを取得
202+ const paragraphSource = new StringSource ( currentNode , {
203+ replacer ( { node, emptyValue } ) {
204+ if ( node . type === "Code" || node . type === "InlineCode" ) {
205+ return emptyValue ( ) ;
206+ }
207+ return undefined ;
208+ }
209+ } ) ;
210+ const paragraphText = paragraphSource . toString ( ) ;
211+
212+ // 「:」で終わる段落の後にリストが続く場合を検出
213+ if ( / [ : : ] [ \s ] * $ / . test ( paragraphText . trim ( ) ) ) {
214+ // 許可パターンのチェック
215+ if ( allows . length > 0 ) {
216+ const matches = matchPatterns ( paragraphText , allows ) ;
217+ if ( matches . length > 0 ) {
218+ continue ;
219+ }
197220 }
198- return undefined ;
221+
222+ documentQualityMetrics . structure ++ ;
223+ hasDocumentIssues = true ;
224+
225+ report ( currentNode , {
226+ message :
227+ "【構造化】コロン(:)で終わる文の直後の箇条書きは機械的な印象を与える可能性があります。「たとえば、次のような点があります。」のような導入文を使った自然な表現を検討してください。"
228+ } ) ;
199229 }
200- } ) ;
201- const text = source . toString ( ) ;
230+ }
231+ }
232+ } ;
233+
234+ /**
235+ * 構造化ガイダンスに関する文書レベルの検出処理
236+ */
237+ const processDocumentStructureGuidance = ( node : any ) => {
238+ if ( disableStructureGuidance ) {
239+ return ;
240+ }
202241
203- // 許可パターンのチェック
204- if ( allows . length > 0 ) {
205- const matches = matchPatterns ( text , allows ) ;
206- if ( matches . length > 0 ) {
207- return ;
242+ // コロン + 箇条書きパターンの検出
243+ detectColonListPattern ( node ) ;
244+ // 将来的にここに他の文書レベルの構造化パターンを追加できます
245+ // 例:
246+ // detectExcessiveNestedLists(node);
247+ // detectInconsistentHeadingStructure(node);
248+ // detectPoorSectionOrganization(node);
249+ // detectInconsistentListFormatting(node);
250+ } ;
251+
252+ /**
253+ * 段落内のガイダンスパターンを検出・報告
254+ */
255+ const processParagraphGuidance = ( node : any ) => {
256+ // StringSourceを使用してコードブロックを除外したテキストを取得
257+ const source = new StringSource ( node , {
258+ replacer ( { node, emptyValue } ) {
259+ // コードブロック、インラインコードを除外
260+ if ( node . type === "Code" || node . type === "InlineCode" ) {
261+ return emptyValue ( ) ;
208262 }
263+ return undefined ;
209264 }
265+ } ) ;
266+ const text = source . toString ( ) ;
210267
211- // 各カテゴリのガイダンスを統合
212- const allGuidancePatterns = [
213- ...( disableRedundancyGuidance ? [ ] : redundancyGuidance ) ,
214- ...( disableVoiceGuidance ? [ ] : voiceGuidance ) ,
215- ...( disableClarityGuidance ? [ ] : clarityGuidance ) ,
216- ...( disableConsistencyGuidance ? [ ] : consistencyGuidance ) ,
217- ...( disableStructureGuidance ? [ ] : structureGuidance )
218- ] ;
268+ // 許可パターンのチェック
269+ if ( allows . length > 0 ) {
270+ const matches = matchPatterns ( text , allows ) ;
271+ if ( matches . length > 0 ) {
272+ return ;
273+ }
274+ }
219275
220- for ( const { pattern, message, category } of allGuidancePatterns ) {
221- const matches = text . matchAll ( pattern ) ;
222- for ( const match of matches ) {
223- const index = match . index ?? 0 ;
276+ // 各カテゴリのガイダンスを統合
277+ const allGuidancePatterns = [
278+ ...( disableRedundancyGuidance ? [ ] : redundancyGuidance ) ,
279+ ...( disableVoiceGuidance ? [ ] : voiceGuidance ) ,
280+ ...( disableClarityGuidance ? [ ] : clarityGuidance ) ,
281+ ...( disableConsistencyGuidance ? [ ] : consistencyGuidance ) ,
282+ ...( disableStructureGuidance ? [ ] : structureGuidance )
283+ ] ;
224284
225- // プレーンテキストの位置を元のノード内の位置に変換
226- const originalIndex = source . originalIndexFromIndex ( index ) ;
227- const originalEndIndex = source . originalIndexFromIndex ( index + match [ 0 ] . length ) ;
285+ for ( const { pattern, message, category } of allGuidancePatterns ) {
286+ const matches = text . matchAll ( pattern ) ;
287+ for ( const match of matches ) {
288+ const index = match . index ?? 0 ;
228289
229- if ( originalIndex !== undefined && originalEndIndex !== undefined ) {
230- const originalRange = [ originalIndex , originalEndIndex ] as const ;
290+ // プレーンテキストの位置を元のノード内の位置に変換
291+ const originalIndex = source . originalIndexFromIndex ( index ) ;
292+ const originalEndIndex = source . originalIndexFromIndex ( index + match [ 0 ] . length ) ;
231293
232- // カテゴリ別のメトリクスを更新
233- documentQualityMetrics [ category as keyof typeof documentQualityMetrics ] ++ ;
234- hasDocumentIssues = true ;
294+ if ( originalIndex !== undefined && originalEndIndex !== undefined ) {
295+ const originalRange = [ originalIndex , originalEndIndex ] as const ;
235296
236- report ( node , {
237- message : message ,
238- padding : locator . range ( originalRange )
239- } ) ;
240- }
241- }
242- }
243- } ,
244- [ Syntax . DocumentExit ] ( node ) {
245- // 文書全体の分析を実行(enableDocumentAnalysisがtrueの場合)
246- if ( enableDocumentAnalysis && hasDocumentIssues ) {
247- const totalIssues = Object . values ( documentQualityMetrics ) . reduce ( ( sum , count ) => sum + count , 0 ) ;
297+ // カテゴリ別のメトリクスを更新
298+ documentQualityMetrics [ category as keyof typeof documentQualityMetrics ] ++ ;
299+ hasDocumentIssues = true ;
248300
249- // カテゴリ別の詳細な分析結果を含むメッセージを生成
250- const categoryDetails = [ ] ;
251- if ( documentQualityMetrics . redundancy > 0 ) {
252- categoryDetails . push ( `簡潔性: ${ documentQualityMetrics . redundancy } 件` ) ;
253- }
254- if ( documentQualityMetrics . voice > 0 ) {
255- categoryDetails . push ( `明確性: ${ documentQualityMetrics . voice } 件` ) ;
256- }
257- if ( documentQualityMetrics . clarity > 0 ) {
258- categoryDetails . push ( `具体性: ${ documentQualityMetrics . clarity } 件` ) ;
259- }
260- if ( documentQualityMetrics . consistency > 0 ) {
261- categoryDetails . push ( `一貫性: ${ documentQualityMetrics . consistency } 件` ) ;
262- }
263- if ( documentQualityMetrics . structure > 0 ) {
264- categoryDetails . push ( `構造化: ${ documentQualityMetrics . structure } 件` ) ;
301+ report ( node , {
302+ message : message ,
303+ padding : locator . range ( originalRange )
304+ } ) ;
265305 }
306+ }
307+ }
308+ } ;
266309
267- const detailsText = categoryDetails . length > 0 ? ` [内訳: ${ categoryDetails . join ( ", " ) } ]` : "" ;
310+ /**
311+ * 文書全体の品質分析結果を報告
312+ */
313+ const processDocumentAnalysis = ( node : any ) => {
314+ // 文書全体の分析を実行(enableDocumentAnalysisがtrueの場合)
315+ if ( enableDocumentAnalysis && hasDocumentIssues ) {
316+ const totalIssues = Object . values ( documentQualityMetrics ) . reduce ( ( sum , count ) => sum + count , 0 ) ;
268317
269- report ( node , {
270- message : `【テクニカルライティング品質分析】この文書で${ totalIssues } 件の改善提案が見つかりました${ detailsText } 。効果的なテクニカルライティングの7つのC(Clear, Concise, Correct, Coherent, Concrete, Complete, Courteous)の原則に基づいて見直しを検討してください。詳細なガイドライン: https://github.com/textlint-ja/textlint-rule-preset-ai-writing/blob/main/docs/tech-writing-guidelines.md`
271- } ) ;
318+ // カテゴリ別の詳細な分析結果を含むメッセージを生成
319+ const categoryDetails = [ ] ;
320+ if ( documentQualityMetrics . redundancy > 0 ) {
321+ categoryDetails . push ( `簡潔性: ${ documentQualityMetrics . redundancy } 件` ) ;
322+ }
323+ if ( documentQualityMetrics . voice > 0 ) {
324+ categoryDetails . push ( `明確性: ${ documentQualityMetrics . voice } 件` ) ;
325+ }
326+ if ( documentQualityMetrics . clarity > 0 ) {
327+ categoryDetails . push ( `具体性: ${ documentQualityMetrics . clarity } 件` ) ;
328+ }
329+ if ( documentQualityMetrics . consistency > 0 ) {
330+ categoryDetails . push ( `一貫性: ${ documentQualityMetrics . consistency } 件` ) ;
331+ }
332+ if ( documentQualityMetrics . structure > 0 ) {
333+ categoryDetails . push ( `構造化: ${ documentQualityMetrics . structure } 件` ) ;
272334 }
335+
336+ const detailsText = categoryDetails . length > 0 ? ` [内訳: ${ categoryDetails . join ( ", " ) } ]` : "" ;
337+
338+ report ( node , {
339+ message : `【テクニカルライティング品質分析】この文書で${ totalIssues } 件の改善提案が見つかりました${ detailsText } 。効果的なテクニカルライティングの7つのC(Clear, Concise, Correct, Coherent, Concrete, Complete, Courteous)の原則に基づいて見直しを検討してください。詳細なガイドライン: https://github.com/textlint-ja/textlint-rule-preset-ai-writing/blob/main/docs/tech-writing-guidelines.md`
340+ } ) ;
341+ }
342+ } ;
343+
344+ return {
345+ [ Syntax . Document ] ( node ) {
346+ // 文書レベルの構造化ガイダンス処理
347+ processDocumentStructureGuidance ( node ) ;
348+ } ,
349+ [ Syntax . Paragraph ] ( node ) {
350+ // 段落内のガイダンスパターンを検出・報告
351+ processParagraphGuidance ( node ) ;
352+ } ,
353+ [ Syntax . DocumentExit ] ( node ) {
354+ // 文書全体の品質分析結果を報告
355+ processDocumentAnalysis ( node ) ;
273356 }
274357 } ;
275358} ;
0 commit comments