Skip to content

Commit fcd4aa3

Browse files
committed
Enable strict-null-checks for api-extractor
1 parent ffca723 commit fcd4aa3

23 files changed

+195
-139
lines changed

apps/api-extractor/src/ApiDefinitionReference.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export default class ApiDefinitionReference {
121121
};
122122

123123
// E.g. @microsoft/sp-core-library:Guid.equals
124-
let parts: string[] = apiReferenceExpr.match(ApiDefinitionReference._packageRegEx);
124+
let parts: string[] | null = apiReferenceExpr.match(ApiDefinitionReference._packageRegEx);
125125
if (parts) {
126126
// parts[1] is of the form ‘@microsoft/sp-core-library’ or ‘sp-core-library’
127127
const scopePackageName: IScopedPackageName = ApiDefinitionReference.parseScopedPackageName(parts[1]);

apps/api-extractor/src/DocElementParser.ts

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export default class DocElementParser {
4343

4444
const markupElements: MarkupBasicElement[] = [];
4545
let parsing: boolean = true;
46-
let token: Token;
46+
let token: Token | undefined;
4747

4848
while (parsing) {
4949
token = tokenizer.peekToken();
@@ -66,7 +66,7 @@ export default class DocElementParser {
6666
documentation.isDocInherited = true;
6767
break;
6868
case '@link' :
69-
const linkMarkupElement: MarkupElement = this.parseLinkTag(documentation, token);
69+
const linkMarkupElement: MarkupElement | undefined = this.parseLinkTag(documentation, token);
7070
if (linkMarkupElement) {
7171
// Push to linkMarkupElement to retain position in the documentation
7272
markupElements.push(linkMarkupElement);
@@ -101,7 +101,7 @@ export default class DocElementParser {
101101
/**
102102
* This method parses the semantic information in an \@link JSDoc tag, creates and returns a
103103
* MarkupElement with the corresponding information. If the corresponding inline tag \@link is
104-
* not formatted correctly an error will be reported.
104+
* not formatted correctly an error will be reported and undefined is returned.
105105
*
106106
* The format for the \@link tag is {\@link URL or API defintion reference | display text}, where
107107
* the '|' is only needed if the optional display text is given.
@@ -112,18 +112,17 @@ export default class DocElementParser {
112112
* \{@link @microsoft/sp-core-library:Guid.newGuid | new Guid Object \}
113113
* \{@link @microsoft/sp-core-library:Guid.newGuid \}
114114
*/
115-
public static parseLinkTag(documentation: ApiDocumentation, tokenItem: Token): MarkupBasicElement {
115+
public static parseLinkTag(documentation: ApiDocumentation, tokenItem: Token): MarkupBasicElement | undefined {
116116
if (!tokenItem.text) {
117117
documentation.reportError('The {@link} tag must include a URL or API item reference');
118-
return;
118+
return undefined;
119119
}
120120

121121
// Make sure there are no extra pipes
122122
const pipeSplitContent: string[] = tokenItem.text.split('|').map(value => {
123-
if (value) {
124-
return value.trim();
125-
}
123+
return value ? value.trim() : value;
126124
});
125+
127126
if (pipeSplitContent.length > 2) {
128127
documentation.reportError('The {@link} tag contains more than one pipe character ("|")');
129128
return undefined;
@@ -136,7 +135,7 @@ export default class DocElementParser {
136135

137136
// If a display name is given, ensure it only contains characters for words.
138137
if (displayTextPart) {
139-
const match: RegExpExecArray | undefined = this._displayTextBadCharacterRegEx.exec(displayTextPart);
138+
const match: RegExpExecArray | null = this._displayTextBadCharacterRegEx.exec(displayTextPart);
140139
if (match) {
141140
documentation.reportError(`The {@link} tag\'s display text contains an unsupported`
142141
+ ` character: "${match[0]}"`);
@@ -162,7 +161,7 @@ export default class DocElementParser {
162161
linkMarkupElement = Markup.createWebLink(displayTextElements, addressPart);
163162
} else {
164163
// we are processing an API definition reference
165-
const apiDefitionRef: ApiDefinitionReference = ApiDefinitionReference.createFromString(
164+
const apiDefitionRef: ApiDefinitionReference | undefined = ApiDefinitionReference.createFromString(
166165
addressPart,
167166
documentation.reportError
168167
);
@@ -212,7 +211,7 @@ export default class DocElementParser {
212211

213212
// Create the IApiDefinitionReference object
214213
// Deconstruct the API reference expression 'scopeName/packageName:exportName.memberName'
215-
const apiDefinitionRef: ApiDefinitionReference = ApiDefinitionReference.createFromString(
214+
const apiDefinitionRef: ApiDefinitionReference | undefined = ApiDefinitionReference.createFromString(
216215
token.text,
217216
documentation.reportError
218217
);
@@ -223,7 +222,7 @@ export default class DocElementParser {
223222
}
224223

225224
// Atempt to locate the apiDefinitionRef
226-
const resolvedAstItem: ResolvedApiItem = documentation.referenceResolver.resolve(
225+
const resolvedAstItem: ResolvedApiItem | undefined = documentation.referenceResolver.resolve(
227226
apiDefinitionRef,
228227
documentation.context.package,
229228
warnings
@@ -254,20 +253,20 @@ export default class DocElementParser {
254253
// Add additional cases if needed
255254
switch (resolvedAstItem.kind) {
256255
case AstItemKind.Function:
257-
documentation.parameters = resolvedAstItem.params;
258-
documentation.returnsMessage = resolvedAstItem.returnsMessage;
256+
documentation.parameters = resolvedAstItem.params || { };
257+
documentation.returnsMessage = resolvedAstItem.returnsMessage || [];
259258
break;
260259
case AstItemKind.Method:
261260
case AstItemKind.Constructor:
262-
documentation.parameters = resolvedAstItem.params;
263-
documentation.returnsMessage = resolvedAstItem.returnsMessage;
261+
documentation.parameters = resolvedAstItem.params || { };
262+
documentation.returnsMessage = resolvedAstItem.returnsMessage || [];
264263
break;
265264
}
266265

267266
// Check if inheritdoc is depreacted
268267
// We need to check if this documentation has a deprecated message
269268
// but it may not appear until after this token.
270-
if (resolvedAstItem.deprecatedMessage.length > 0) {
269+
if (resolvedAstItem.deprecatedMessage && resolvedAstItem.deprecatedMessage.length > 0) {
271270
documentation.isDocInheritedDeprecated = true;
272271
}
273272
}

apps/api-extractor/src/DocItemLoader.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import AstItemContainer from './ast/AstItemContainer';
1818
import AstPackage from './ast/AstPackage';
1919
import ResolvedApiItem from './ResolvedApiItem';
2020
import { ApiJsonFile } from './api/ApiJsonFile';
21+
import { IReferenceResolver } from './aedoc/ApiDocumentation';
2122

2223
/**
2324
* Used to describe a parsed package name in the form of
@@ -44,7 +45,7 @@ export interface IParsedScopeName {
4445
* To use DocItemLoader: provide a projectFolder to construct a instance of the DocItemLoader,
4546
* then use DocItemLoader.getItem to retrieve the ApiItem of a particular API item.
4647
*/
47-
export default class DocItemLoader {
48+
export default class DocItemLoader implements IReferenceResolver {
4849
private _cache: Map<string, IApiPackage>;
4950
private _projectFolder: string; // Root directory to check for node modules
5051

@@ -66,7 +67,7 @@ export default class DocItemLoader {
6667
*/
6768
public resolve(apiDefinitionRef: ApiDefinitionReference,
6869
astPackage: AstPackage,
69-
warnings: string[]): ResolvedApiItem {
70+
warnings: string[]): ResolvedApiItem | undefined {
7071

7172
// We determine if an 'apiDefinitionRef' is local if it has no package name or if the scoped
7273
// package name is equal to the current package's scoped package name.
@@ -89,9 +90,9 @@ export default class DocItemLoader {
8990
*/
9091
public resolveLocalReferences(apiDefinitionRef: ApiDefinitionReference,
9192
astPackage: AstPackage,
92-
warnings: string[]): ResolvedApiItem {
93+
warnings: string[]): ResolvedApiItem | undefined {
9394

94-
let astItem: AstItem = astPackage.getMemberItem(apiDefinitionRef.exportName);
95+
let astItem: AstItem | undefined = astPackage.getMemberItem(apiDefinitionRef.exportName);
9596
// Check if export name was not found
9697
if (!astItem) {
9798
warnings.push(`Unable to find referenced export \"${apiDefinitionRef.toExportString()}\"`);
@@ -127,10 +128,10 @@ export default class DocItemLoader {
127128
* that is associated with the apiDefinitionRef.
128129
*/
129130
public resolveJsonReferences(apiDefinitionRef: ApiDefinitionReference,
130-
warnings: string[]): ResolvedApiItem {
131+
warnings: string[]): ResolvedApiItem | undefined {
131132

132133
// Check if package can be not found
133-
const docPackage: IApiPackage = this.getPackage(apiDefinitionRef);
134+
const docPackage: IApiPackage | undefined = this.getPackage(apiDefinitionRef);
134135
if (!docPackage) {
135136
// package not found in node_modules
136137
warnings.push(`Unable to find a documentation file (\"${apiDefinitionRef.packageName}.api.json\")` +
@@ -149,7 +150,7 @@ export default class DocItemLoader {
149150

150151
// If memberName exists then check for the existence of the name
151152
if (apiDefinitionRef.memberName) {
152-
let member: ApiMember = undefined;
153+
let member: ApiMember | undefined = undefined;
153154
switch (docItem.kind) {
154155
case 'class':
155156

@@ -190,7 +191,7 @@ export default class DocItemLoader {
190191
*
191192
* @param apiDefinitionRef - interface with properties pertaining to the API definition reference
192193
*/
193-
public getPackage(apiDefinitionRef: ApiDefinitionReference): IApiPackage {
194+
public getPackage(apiDefinitionRef: ApiDefinitionReference): IApiPackage | undefined {
194195
let cachePackageName: string = '';
195196

196197
// We concatenate the scopeName and packageName in case there are packageName conflicts
@@ -201,7 +202,7 @@ export default class DocItemLoader {
201202
}
202203
// Check if package exists in cache
203204
if (this._cache.has(cachePackageName)) {
204-
return this._cache.get(cachePackageName);
205+
return this._cache.get(cachePackageName);
205206
}
206207

207208
// Doesn't exist in cache, attempt to load the json file

apps/api-extractor/src/ExtractorContext.ts

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,12 @@ export class ExtractorContext {
6868

6969
this.policies = options.policies;
7070

71-
this._packageFolder = this.packageJsonLookup.tryGetPackageFolder(options.entryPointFile);
71+
const folder: string | undefined = this.packageJsonLookup.tryGetPackageFolder(options.entryPointFile);
72+
if (!folder) {
73+
throw new Error('Unable to find a package.json for entry point: ' + options.entryPointFile);
74+
}
75+
this._packageFolder = folder;
76+
7277
this._packageName = this.packageJsonLookup.getPackageName(this._packageFolder);
7378

7479
this.docItemLoader = new DocItemLoader(this._packageFolder);
@@ -111,17 +116,21 @@ export class ExtractorContext {
111116
/**
112117
* Reports an error message to the registered ApiErrorHandler.
113118
*/
114-
public reportError(message: string, sourceFile: ts.SourceFile, start: number): void {
115-
const lineAndCharacter: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(start);
116-
117-
// If the file is under the packageFolder, then show a relative path
118-
const relativePath: string = path.relative(this.packageFolder, sourceFile.fileName);
119-
const shownPath: string = relativePath.substr(0, 2) === '..' ? sourceFile.fileName : relativePath;
120-
121-
// Format the error so that VS Code can follow it. For example:
122-
// "src\MyClass.ts(15,1): The JSDoc tag "@blah" is not supported by AEDoc"
123-
this._logger.logError(`${shownPath}(${lineAndCharacter.line + 1},${lineAndCharacter.character + 1}): `
124-
+ message);
119+
public reportError(message: string, sourceFile: ts.SourceFile | undefined, start: number | undefined): void {
120+
if (sourceFile && start) {
121+
const lineAndCharacter: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(start);
122+
123+
// If the file is under the packageFolder, then show a relative path
124+
const relativePath: string = path.relative(this.packageFolder, sourceFile.fileName);
125+
const shownPath: string = relativePath.substr(0, 2) === '..' ? sourceFile.fileName : relativePath;
126+
127+
// Format the error so that VS Code can follow it. For example:
128+
// "src\MyClass.ts(15,1): The JSDoc tag "@blah" is not supported by AEDoc"
129+
this._logger.logError(`${shownPath}(${lineAndCharacter.line + 1},${lineAndCharacter.character + 1}): `
130+
+ message);
131+
} else {
132+
this._logger.logError(message);
133+
}
125134
}
126135

127136
/**
@@ -142,7 +151,10 @@ export class ExtractorContext {
142151
files.forEach(file => {
143152
if (path.extname(file) === '.json') {
144153
const externalJsonFilePath: string = path.join(externalJsonCollectionPath, file);
145-
this.docItemLoader.loadPackageIntoCache(externalJsonFilePath, path.parse(file).name.split('.').shift());
154+
155+
// Example: "C:\Example\my-package.json" --> "my-package"
156+
const packageName: string = path.parse(file).name;
157+
this.docItemLoader.loadPackageIntoCache(externalJsonFilePath, packageName);
146158
}
147159
});
148160
}

apps/api-extractor/src/ResolvedApiItem.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ export default class ResolvedApiItem {
1616
public kind: AstItemKind;
1717
public summary: MarkupElement[];
1818
public remarks: MarkupElement[];
19-
public deprecatedMessage: MarkupBasicElement[];
19+
public deprecatedMessage: MarkupBasicElement[] | undefined;
2020
public releaseTag: ReleaseTag;
2121
public isBeta: boolean;
22-
public params: {[name: string]: IAedocParameter};
23-
public returnsMessage: MarkupBasicElement[];
22+
public params: { [name: string]: IAedocParameter } | undefined;
23+
public returnsMessage: MarkupBasicElement[] | undefined;
2424
/**
2525
* This property will either be an AstItem or undefined.
2626
*/
27-
public astItem: AstItem;
27+
public astItem: AstItem | undefined;
2828

2929
/**
3030
* A function to abstract the construction of a ResolvedApiItem instance
@@ -49,8 +49,8 @@ export default class ResolvedApiItem {
4949
* from a JSON object that symbolizes an ApiItem.
5050
*/
5151
public static createFromJson(docItem: ApiItem): ResolvedApiItem {
52-
let parameters: {[name: string]: IAedocParameter} = undefined;
53-
let returnsMessage: MarkupBasicElement[] = undefined;
52+
let parameters: { [name: string]: IAedocParameter } | undefined = undefined;
53+
let returnsMessage: MarkupBasicElement[] | undefined = undefined;
5454
switch (docItem.kind) {
5555
case 'function':
5656
parameters = docItem.parameters;
@@ -81,12 +81,12 @@ export default class ResolvedApiItem {
8181
kind: AstItemKind,
8282
summary: MarkupElement[],
8383
remarks: MarkupElement[],
84-
deprecatedMessage: MarkupBasicElement[],
84+
deprecatedMessage: MarkupBasicElement[] | undefined,
8585
isBeta: boolean,
86-
params: {[name: string]: IAedocParameter},
87-
returnsMessage: MarkupBasicElement[],
86+
params: { [name: string]: IAedocParameter } | undefined,
87+
returnsMessage: MarkupBasicElement[] | undefined,
8888
releaseTag: ReleaseTag,
89-
astItem: AstItem) {
89+
astItem: AstItem | undefined) {
9090
this.kind = kind;
9191
this.summary = summary;
9292
this.remarks = remarks;

apps/api-extractor/src/TypeScriptHelpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ export default class TypeScriptHelpers {
140140

141141
const result: string[] = [];
142142
let index: number = 0;
143-
let match: RegExpExecArray;
143+
let match: RegExpExecArray | null;
144144

145145
do {
146146
match = regExp.exec(text);

0 commit comments

Comments
 (0)