Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[api-extractor] Implement "ae-internal-missing-underscore" warning #1170

Merged
merged 6 commits into from
Mar 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions apps/api-extractor/src/api/ExtractorMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ import * as path from 'path';
import { ExtractorMessageId } from './ExtractorMessageId';
import { Path, Text } from '@microsoft/node-core-library';

/**
* Used by {@link ExtractorMessage.properties}.
*
* @public
*/
export interface IExtractorMessageProperties {

/**
* A declaration can have multiple names if it is exported more than once.
* If an `ExtractorMessage` applies to a specific export name, this property can indicate that.
*
* @remarks
*
* Used by {@link ExtractorMessageId.InternalMissingUnderscore}.
*/
readonly exportName?: string;
}

/**
* Specifies a category of messages for use with {@link ExtractorMessage}.
* @public
Expand Down Expand Up @@ -49,6 +67,7 @@ export interface IExtractorMessageOptions {
sourceFilePath?: string;
sourceFileLine?: number;
sourceFileColumn?: number;
properties?: IExtractorMessageProperties;
}

/**
Expand Down Expand Up @@ -90,6 +109,12 @@ export class ExtractorMessage {
*/
public readonly sourceFileColumn: number | undefined;

/**
* Additional contextual information about the message that may be useful when reporting errors.
* All properties are optional.
*/
public readonly properties: IExtractorMessageProperties;

/** @internal */
public constructor(options: IExtractorMessageOptions) {
this.category = options.category;
Expand All @@ -98,6 +123,7 @@ export class ExtractorMessage {
this.sourceFilePath = options.sourceFilePath;
this.sourceFileLine = options.sourceFileLine;
this.sourceFileColumn = options.sourceFileColumn;
this.properties = options.properties || { };
}

/**
Expand Down
10 changes: 8 additions & 2 deletions apps/api-extractor/src/api/ExtractorMessageId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ export const enum ExtractorMessageId {
/**
* The symbol ___ needs to be exported by the entry point ___.
*/
ForgottenExport = 'ae-forgotten-export'
ForgottenExport = 'ae-forgotten-export',

/**
* The name ___ should be prefixed with an underscore because the declaration is marked as `@internal`.
*/
InternalMissingUnderscore = 'ae-internal-missing-underscore'
}

export const allExtractorMessageIds: Set<string> = new Set<string>([
Expand All @@ -49,5 +54,6 @@ export const allExtractorMessageIds: Set<string> = new Set<string>([
'ae-incompatible-release-tags',
'ae-missing-release-tag',
'ae-misplaced-package-tag',
'ae-forgotten-export'
'ae-forgotten-export',
'ae-internal-missing-underscore'
]);
12 changes: 7 additions & 5 deletions apps/api-extractor/src/collector/MessageRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { AstSymbol } from '../analyzer/AstSymbol';
import {
ExtractorMessage,
ExtractorMessageCategory,
IExtractorMessageOptions
IExtractorMessageOptions,
IExtractorMessageProperties
} from '../api/ExtractorMessage';
import { ExtractorMessageId, allExtractorMessageIds } from '../api/ExtractorMessageId';
import {
Expand Down Expand Up @@ -164,7 +165,7 @@ export class MessageRouter {
* Add a message from the API Extractor analysis
*/
public addAnalyzerIssue(messageId: ExtractorMessageId, messageText: string,
astDeclarationOrSymbol: AstDeclaration | AstSymbol): void {
astDeclarationOrSymbol: AstDeclaration | AstSymbol, properties?: IExtractorMessageProperties): void {

let astDeclaration: AstDeclaration;
if (astDeclarationOrSymbol instanceof AstDeclaration) {
Expand All @@ -175,7 +176,7 @@ export class MessageRouter {

const extractorMessage: ExtractorMessage = this.addAnalyzerIssueForPosition(
messageId, messageText, astDeclaration.declaration.getSourceFile(),
astDeclaration.declaration.getStart());
astDeclaration.declaration.getStart(), properties);

this._associateMessageWithAstDeclaration(extractorMessage, astDeclaration);
}
Expand Down Expand Up @@ -231,7 +232,7 @@ export class MessageRouter {
* Add a message for a location in an arbitrary source file.
*/
public addAnalyzerIssueForPosition(messageId: ExtractorMessageId, messageText: string,
sourceFile: ts.SourceFile, pos: number): ExtractorMessage {
sourceFile: ts.SourceFile, pos: number, properties?: IExtractorMessageProperties): ExtractorMessage {

const lineAndCharacter: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(
pos);
Expand All @@ -242,7 +243,8 @@ export class MessageRouter {
text: messageText,
sourceFilePath: sourceFile.fileName,
sourceFileLine: lineAndCharacter.line + 1,
sourceFileColumn: lineAndCharacter.character + 1
sourceFileColumn: lineAndCharacter.character + 1,
properties
};

this._sourceMapper.updateExtractorMessageOptions(options);
Expand Down
22 changes: 22 additions & 0 deletions apps/api-extractor/src/collector/VisibilityChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,33 @@ export class VisibilityChecker {
VisibilityChecker._checkReferences(collector, astDeclaration, alreadyWarnedSymbols);
});

VisibilityChecker._checkForInternalUnderscore(collector, entity, entity.astEntity);
}
}
}
}

private static _checkForInternalUnderscore(collector: Collector, collectorEntity: CollectorEntity,
astSymbol: AstSymbol): void {

const astSymbolMetadata: SymbolMetadata = collector.fetchMetadata(astSymbol);

if (astSymbolMetadata.releaseTag === ReleaseTag.Internal && !astSymbolMetadata.releaseTagSameAsParent) {
for (const exportName of collectorEntity.exportNames) {
if (exportName[0] !== '_') {
collector.messageRouter.addAnalyzerIssue(
ExtractorMessageId.InternalMissingUnderscore,
`The name ${exportName} should be prefixed with an underscore`
+ ` because the declaration is marked as "@internal"`,
astSymbol,
{ exportName }
);
}
}
}

}

private static _checkReferences(collector: Collector, astDeclaration: AstDeclaration,
alreadyWarnedSymbols: Set<AstSymbol>): void {

Expand Down
73 changes: 57 additions & 16 deletions apps/api-extractor/src/generators/ReviewFileGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,46 @@ export class ReviewFileGenerator {
// Emit the regular declarations
for (const entity of collector.entities) {
if (entity.exported) {

// First, collect the list of export names for this symbol. When reporting messages with
// ExtractorMessage.properties.exportName, this will enable us to emit the warning comments alongside
// the associated export statement.
interface IExportToEmit {
readonly exportName: string;
readonly associatedMessages: ExtractorMessage[];
}
const exportsToEmit: Map<string, IExportToEmit> = new Map<string, IExportToEmit>();

for (const exportName of entity.exportNames) {
if (!entity.shouldInlineExport) {
exportsToEmit.set(exportName, { exportName, associatedMessages: [] });
}
}

if (entity.astEntity instanceof AstSymbol) {
// Emit all the declarations for this entry
// Emit all the declarations for this entity
for (const astDeclaration of entity.astEntity.astDeclarations || []) {

stringWriter.write(ReviewFileGenerator._getAedocSynopsis(collector, astDeclaration));
// Get the messages associated with this declaration
const fetchedMessages: ExtractorMessage[] = collector.messageRouter
.fetchAssociatedMessagesForReviewFile(astDeclaration);

// Peel off the messages associated with an export statement and store them
// in IExportToEmit.associatedMessages (to be processed later). The remaining messages will
// added to messagesToReport, to be emitted next to the declaration instead of the export statement.
const messagesToReport: ExtractorMessage[] = [];
for (const message of fetchedMessages) {
if (message.properties.exportName) {
const exportToEmit: IExportToEmit | undefined = exportsToEmit.get(message.properties.exportName);
if (exportToEmit) {
exportToEmit.associatedMessages.push(message);
continue;
}
}
messagesToReport.push(message);
}

stringWriter.write(ReviewFileGenerator._getAedocSynopsis(collector, astDeclaration, messagesToReport));

const span: Span = new Span(astDeclaration.declaration);
ReviewFileGenerator._modifySpan(collector, span, entity, astDeclaration, false);
Expand All @@ -75,11 +110,16 @@ export class ReviewFileGenerator {
}
}

for (const exportName of entity.exportNames) {
if (!entity.shouldInlineExport) {
DtsEmitHelpers.emitNamedExport(stringWriter, exportName, entity);
stringWriter.writeLine();
// Now emit the export statements for this entity.
for (const exportToEmit of exportsToEmit.values()) {
// Write any associated messages
for (const message of exportToEmit.associatedMessages) {
ReviewFileGenerator._writeLineAsComments(stringWriter,
'Warning: ' + message.formatMessageWithoutLocation());
}

DtsEmitHelpers.emitNamedExport(stringWriter, exportToEmit.exportName, entity);
stringWriter.writeLine();
}
}
}
Expand Down Expand Up @@ -245,7 +285,10 @@ export class ReviewFileGenerator {
}

if (!insideTypeLiteral) {
const aedocSynopsis: string = ReviewFileGenerator._getAedocSynopsis(collector, childAstDeclaration);
const messagesToReport: ExtractorMessage[] = collector.messageRouter
.fetchAssociatedMessagesForReviewFile(childAstDeclaration);
const aedocSynopsis: string = ReviewFileGenerator._getAedocSynopsis(collector, childAstDeclaration,
messagesToReport);
const indentedAedocSynopsis: string = ReviewFileGenerator._addIndentAfterNewlines(aedocSynopsis,
child.getIndent());

Expand All @@ -263,14 +306,12 @@ export class ReviewFileGenerator {
* whether the item has been documented, and any warnings that were detected
* by the analysis.
*/
private static _getAedocSynopsis(collector: Collector, astDeclaration: AstDeclaration): string {
const output: StringWriter = new StringWriter();

const messagesToReport: ExtractorMessage[] = collector.messageRouter
.fetchAssociatedMessagesForReviewFile(astDeclaration);
private static _getAedocSynopsis(collector: Collector, astDeclaration: AstDeclaration,
messagesToReport: ExtractorMessage[]): string {
const stringWriter: StringWriter = new StringWriter();

for (const message of messagesToReport) {
ReviewFileGenerator._writeLineAsComments(output, 'Warning: ' + message.formatMessageWithoutLocation());
ReviewFileGenerator._writeLineAsComments(stringWriter, 'Warning: ' + message.formatMessageWithoutLocation());
}

const declarationMetadata: DeclarationMetadata = collector.fetchMetadata(astDeclaration);
Expand Down Expand Up @@ -312,13 +353,13 @@ export class ReviewFileGenerator {

if (footerParts.length > 0) {
if (messagesToReport.length > 0) {
ReviewFileGenerator._writeLineAsComments(output, ''); // skip a line after the warnings
ReviewFileGenerator._writeLineAsComments(stringWriter, ''); // skip a line after the warnings
}

ReviewFileGenerator._writeLineAsComments(output, footerParts.join(' '));
ReviewFileGenerator._writeLineAsComments(stringWriter, footerParts.join(' '));
}

return output.toString();
return stringWriter.toString();
}

private static _writeLineAsComments(stringWriter: StringWriter, line: string): void {
Expand Down
6 changes: 5 additions & 1 deletion apps/api-extractor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
*/

export { Extractor, IAnalyzeProjectOptions, IExtractorOptions } from './api/Extractor';
export { ExtractorMessage, ExtractorMessageCategory } from './api/ExtractorMessage';
export {
ExtractorMessage,
IExtractorMessageProperties,
ExtractorMessageCategory
} from './api/ExtractorMessage';
export { ExtractorMessageId } from './api/ExtractorMessageId';
export {
IExtractorTsconfigCompilerConfig,
Expand Down
4 changes: 4 additions & 0 deletions apps/api-extractor/src/schemas/api-extractor-defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@
"ae-incompatible-release-tags": {
"logLevel": "warning",
"addToApiReviewFile": true
},
"ae-internal-missing-underscore": {
"logLevel": "warning",
"addToApiReviewFile": true
}
},
"tsdocMessageReporting": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,6 @@
"canonicalReference": "",
"name": "",
"members": [
{
"kind": "Class",
"canonicalReference": "(A:class)",
"docComment": "/**\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "declare class "
},
{
"kind": "Reference",
"text": "A"
},
{
"kind": "Content",
"text": " "
}
],
"releaseTag": "Public",
"name": "A",
"members": [],
"implementsTokenRanges": []
},
{
"kind": "Class",
"canonicalReference": "(X:class)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
```ts

// @public (undocumented)
// @internal (undocumented)
class A {
}

// Warning: (ae-internal-missing-underscore) The name B should be prefixed with an underscore because the declaration is marked as "@internal"
export { A as B }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an example where the warning is associated with the export statement.


// Warning: (ae-internal-missing-underscore) The name C should be prefixed with an underscore because the declaration is marked as "@internal"
export { A as C }

// @public (undocumented)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

/** @public */
/** @internal */
declare class A {
}
export { A as B }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
export class X { }
export { X as Y }

/** @public */
/** @internal */
class A { }
// The underscore warning should get printed next to these export statements, not next to the class declaration
export { A as B }
export { A as C }
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,15 @@ export namespace EntangledNamespace {
// @alpha
export type ExportedAlias = AlphaClass;

// Warning: (ae-internal-missing-underscore) The name InternalClass should be prefixed with an underscore because the declaration is marked as "@internal"
//
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case the warning is associated with the class declaration, since it uses an inline export.

// @internal
export class InternalClass {
undecoratedMember(): void;
}

// Warning: (ae-internal-missing-underscore) The name IPublicClassInternalParameters should be prefixed with an underscore because the declaration is marked as "@internal"
//
// @internal
export interface IPublicClassInternalParameters {
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@microsoft/api-extractor",
"comment": "Reintroduce \"ae-internal-missing-underscore\" warning for API items marked as `@internal` but whose name does not start with an underscore",
"type": "patch"
}
],
"packageName": "@microsoft/api-extractor",
"email": "[email protected]"
}
Loading