@@ -186,90 +186,173 @@ const rule: TextlintRuleModule<Options> = (context, options = {}) => {
186
186
structure : 0
187
187
} ;
188
188
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
+ }
197
220
}
198
- return undefined ;
221
+
222
+ documentQualityMetrics . structure ++ ;
223
+ hasDocumentIssues = true ;
224
+
225
+ report ( currentNode , {
226
+ message :
227
+ "【構造化】コロン(:)で終わる文の直後の箇条書きは機械的な印象を与える可能性があります。「たとえば、次のような点があります。」のような導入文を使った自然な表現を検討してください。"
228
+ } ) ;
199
229
}
200
- } ) ;
201
- const text = source . toString ( ) ;
230
+ }
231
+ }
232
+ } ;
233
+
234
+ /**
235
+ * 構造化ガイダンスに関する文書レベルの検出処理
236
+ */
237
+ const processDocumentStructureGuidance = ( node : any ) => {
238
+ if ( disableStructureGuidance ) {
239
+ return ;
240
+ }
202
241
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 ( ) ;
208
262
}
263
+ return undefined ;
209
264
}
265
+ } ) ;
266
+ const text = source . toString ( ) ;
210
267
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
+ }
219
275
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
+ ] ;
224
284
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 ;
228
289
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 ) ;
231
293
232
- // カテゴリ別のメトリクスを更新
233
- documentQualityMetrics [ category as keyof typeof documentQualityMetrics ] ++ ;
234
- hasDocumentIssues = true ;
294
+ if ( originalIndex !== undefined && originalEndIndex !== undefined ) {
295
+ const originalRange = [ originalIndex , originalEndIndex ] as const ;
235
296
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 ;
248
300
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
+ } ) ;
265
305
}
306
+ }
307
+ }
308
+ } ;
266
309
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 ) ;
268
317
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 } 件` ) ;
272
334
}
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 ) ;
273
356
}
274
357
} ;
275
358
} ;
0 commit comments