| import { |
| createRule, |
| defineCodeFix, |
| getDoc, |
| getSourceLocation, |
| paramMessage, |
| } from '@typespec/compiler' |
| import * as prettier from 'prettier' |
| import { |
| detectNewline, |
| extractMarkdownFromDocComment, |
| getIndentBefore, |
| wrapMarkdownAsDocComment, |
| } from './utils.js' |
|
|
| export const docDecoratorRule = createRule({ |
| name: 'doc-decorator', |
| severity: 'warning', |
| description: 'Ensure documentation.', |
| messages: { |
| default: paramMessage`Missing documentation for ${'name'} ${'type'}`, |
| }, |
| create: (context) => ({ |
| model: (target) => { |
| if (target.name && !getDoc(context.program, target)) { |
| context.reportDiagnostic({ |
| target, |
| format: { |
| name: target.name, |
| }, |
| }) |
| } |
|
|
| if (target.name.endsWith('Response')) { |
| return |
| } |
|
|
| for (const [name, property] of target.properties) { |
| if ( |
| target.name && |
| name && |
| !['_', 'contentType'].includes(name) && |
| !getDoc(context.program, property) |
| ) { |
| context.reportDiagnostic({ |
| target: property, |
| format: { |
| name: `${target.name}.${name}`, |
| }, |
| }) |
| } |
| } |
| }, |
| enum: (target) => { |
| if (target.name && !getDoc(context.program, target)) { |
| context.reportDiagnostic({ |
| target, |
| format: { |
| name: target.name, |
| }, |
| }) |
| } |
| }, |
| union: (target) => { |
| if (target.name && !getDoc(context.program, target)) { |
| context.reportDiagnostic({ |
| target, |
| format: { |
| name: target.name, |
| }, |
| }) |
| } |
| }, |
| }), |
| }) |
|
|
| |
| |
| |
| |
| |
| |
| async function formatDocMarkdown(markdown, options = {}) { |
| if (markdown.trim() === '') return '' |
| return await prettier.format(markdown, { |
| parser: 'markdown', |
| printWidth: options.printWidth ?? 80, |
| proseWrap: options.proseWrap ?? 'always', |
| }) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function createFormatDocCommentCodeFix(location, newText) { |
| return defineCodeFix({ |
| id: 'format-doc-comment', |
| label: 'Format doc comment', |
| fix(context) { |
| return context.replaceText(location, newText) |
| }, |
| }) |
| } |
|
|
| |
| |
| |
| |
| |
| function collectDocNodes(target, sink) { |
| const node = target.node |
| if (!node || !node.docs || node.docs.length === 0) return |
| for (const doc of node.docs) sink.push(doc) |
| } |
|
|
| export const docFormatRule = createRule({ |
| name: 'doc-format', |
| severity: 'warning', |
| description: |
| 'Format doc comment bodies as Markdown using Prettier (proseWrap=always).', |
| messages: { |
| default: |
| 'Doc comment is not formatted. Apply the suggested fix to reformat as Markdown.', |
| }, |
| |
| async: true, |
| create: (context) => { |
| |
| const docNodes = [] |
|
|
| const collect = (target) => collectDocNodes(target, docNodes) |
|
|
| return { |
| model: collect, |
| modelProperty: collect, |
| enum: collect, |
| enumMember: collect, |
| union: collect, |
| unionVariant: collect, |
| operation: collect, |
| interface: collect, |
| scalar: collect, |
| namespace: collect, |
|
|
| async exit() { |
| |
| const seen = new Set() |
| const work = [] |
| for (const doc of docNodes) { |
| if (seen.has(doc)) continue |
| seen.add(doc) |
| work.push(processDoc(doc, context)) |
| } |
| await Promise.all(work) |
| }, |
| } |
| }, |
| }) |
|
|
| |
| |
| |
| |
| |
| |
| async function processDoc(doc, context) { |
| const location = getSourceLocation(doc) |
| const source = location.file.text |
| const raw = source.slice(location.pos, location.end) |
|
|
| |
| if (!raw.startsWith('/**') || !raw.endsWith('*/')) return |
|
|
| const indent = getIndentBefore(source, location.pos) |
| const newline = detectNewline(source) |
|
|
| let markdown |
| try { |
| markdown = extractMarkdownFromDocComment(raw) |
| } catch { |
| return |
| } |
|
|
| let formatted |
| try { |
| formatted = await formatDocMarkdown(markdown) |
| } catch { |
| |
| return |
| } |
|
|
| const replacement = wrapMarkdownAsDocComment(formatted, indent, newline) |
| if (replacement === raw) return |
|
|
| context.reportDiagnostic({ |
| target: doc, |
| codefixes: [createFormatDocCommentCodeFix(location, replacement)], |
| }) |
| } |
|
|