Skip to content

Commit 265c304

Browse files
authored
feat(jsii): check that referenced @params exist (#431)
Verify integrity of the documentation by forcing the parameter names referred to in @param declarations to actually exist. Right now it's a WARNING, will be turned into an ERROR some time in the future. Scrub your sources! Fixes #422.
1 parent afbabff commit 265c304

File tree

2 files changed

+42
-12
lines changed

2 files changed

+42
-12
lines changed

packages/jsii/lib/assembler.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import log4js = require('log4js');
77
import path = require('path');
88
import ts = require('typescript');
99
import { JSII_DIAGNOSTICS_CODE } from './compiler';
10-
import { parseSymbolDocumentation } from './docs';
10+
import { getReferencedDocParams, parseSymbolDocumentation } from './docs';
1111
import { Diagnostic, Emitter } from './emitter';
1212
import literate = require('./literate');
1313
import { ProjectInfo } from './project-info';
@@ -645,18 +645,10 @@ export class Assembler implements Emitter {
645645
}
646646

647647
/**
648-
* Register documentations on a ``spec.Documentable`` entry.
649-
*
650-
* @param sym the symbol holding the JSDoc information
651-
* @param documentable the entity being documented
652-
*
653-
* @returns ``documentable``
648+
* Return docs for a symbol
654649
*/
655650
private _visitDocumentation(sym: ts.Symbol): spec.Docs | undefined {
656-
const comment = ts.displayPartsToString(sym.getDocumentationComment(this._typeChecker)).trim();
657-
658-
// Right here we'll just guess that the first declaration site is the most important one.
659-
const result = parseSymbolDocumentation(comment, sym.getJsDocTags());
651+
const result = parseSymbolDocumentation(sym, this._typeChecker);
660652

661653
for (const diag of result.diagnostics || []) {
662654
this._diagnostic(sym.declarations[0],
@@ -669,6 +661,20 @@ export class Assembler implements Emitter {
669661
return !allUndefined ? result.docs : undefined;
670662
}
671663

664+
/**
665+
* Check that all parameters the doc block refers to with a @param declaration actually exist
666+
*/
667+
private _validateReferencedDocParams(method: spec.Method, methodSym: ts.Symbol) {
668+
const params = getReferencedDocParams(methodSym);
669+
const actualNames = new Set((method.parameters || []).map(p => p.name));
670+
for (const param of params) {
671+
if (!actualNames.has(param)) {
672+
this._diagnostic(methodSym.valueDeclaration, ts.DiagnosticCategory.Warning,
673+
`In doc block of '${method.name}', '@param ${param}' refers to a nonexistent parameter.`);
674+
}
675+
}
676+
}
677+
672678
private async _visitInterface(type: ts.Type, namespace: string[]): Promise<spec.InterfaceType | undefined> {
673679
if (LOG.isTraceEnabled()) {
674680
LOG.trace(`Processing interface: ${colors.gray(namespace.join('.'))}.${colors.cyan(type.symbol.name)}`);
@@ -846,6 +852,8 @@ export class Assembler implements Emitter {
846852
});
847853
}
848854

855+
this._validateReferencedDocParams(method, symbol);
856+
849857
type.methods = type.methods || [];
850858
if (type.methods.find(m => m.name === method.name && m.static === method.static) != null) {
851859
LOG.trace(`Dropping re-declaration of ${colors.green(type.fqn)}#${colors.cyan(method.name!)}`);

packages/jsii/lib/docs.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,29 @@
3333
import spec = require('jsii-spec');
3434
import ts = require('typescript');
3535

36-
export function parseSymbolDocumentation(comments: string | undefined, tags: ts.JSDocTagInfo[]): DocsParsingResult {
36+
export function parseSymbolDocumentation(sym: ts.Symbol, typeChecker: ts.TypeChecker): DocsParsingResult {
37+
const comment = ts.displayPartsToString(sym.getDocumentationComment(typeChecker)).trim();
38+
const tags = sym.getJsDocTags();
39+
40+
// Right here we'll just guess that the first declaration site is the most important one.
41+
return parseDocParts(comment, tags);
42+
}
43+
44+
/**
45+
* Return the list of parameter names that are referenced in the docstring for this symbol
46+
*/
47+
export function getReferencedDocParams(sym: ts.Symbol): string[] {
48+
const ret = new Array<string>();
49+
for (const tag of sym.getJsDocTags()) {
50+
if (tag.name === 'param') {
51+
const parts = (tag.text || '').split(' ');
52+
ret.push(parts[0]);
53+
}
54+
}
55+
return ret;
56+
}
57+
58+
function parseDocParts(comments: string | undefined, tags: ts.JSDocTagInfo[]): DocsParsingResult {
3759
const diagnostics = new Array<string>();
3860
const docs: spec.Docs = {};
3961

0 commit comments

Comments
 (0)