Refactoring.
This commit is contained in:
Родитель
945c96b23f
Коммит
0e95c08bac
|
@ -1,30 +0,0 @@
|
|||
import { ApiTreeNode } from "./api-tree-node.js";
|
||||
import { ApiView } from "./apiview.js";
|
||||
import { ApiViewDiagnostic } from "./diagnostic.js";
|
||||
|
||||
/**
|
||||
* The serializable form of the ApiView. This is the tokenfile that gets sent to the APIView service.
|
||||
*/
|
||||
export class ApiViewDocument {
|
||||
name: string;
|
||||
packageName: string;
|
||||
apiForest: ApiTreeNode[] | null;
|
||||
diagnostics: ApiViewDiagnostic[];
|
||||
versionString: string;
|
||||
language: string;
|
||||
crossLanguagePackageId: string | undefined;
|
||||
|
||||
constructor(apiview: ApiView) {
|
||||
this.name = apiview.name;
|
||||
this.packageName = apiview.packageName;
|
||||
this.apiForest = apiview.nodes;
|
||||
this.diagnostics = apiview.diagnostics;
|
||||
this.versionString = apiview.versionString;
|
||||
this.language = "TypeSpec";
|
||||
this.crossLanguagePackageId = apiview.crossLanguagePackageId;
|
||||
}
|
||||
|
||||
asString(): string {
|
||||
return JSON.stringify(this, null, 2);
|
||||
}
|
||||
}
|
|
@ -1,24 +1,25 @@
|
|||
import { getNamespaceFullName, Namespace, navigateProgram, Program } from "@typespec/compiler";
|
||||
import { ApiViewDiagnostic, ApiViewDiagnosticLevel } from "./diagnostic.js";
|
||||
import { CodeDiagnostic, CodeDiagnosticLevel } from "./models/code-diagnostic.js";
|
||||
import { NamespaceModel } from "./namespace-model.js";
|
||||
import { LIB_VERSION } from "./version.js";
|
||||
import { ApiTreeNode, NodeKind, NodeOptions, NodeTag } from "./api-tree-node.js";
|
||||
import { ApiTreeNode, ApiTreeNodeKind, ApiTreeNodeOptions, NodeTag } from "./models/api-tree-node.js";
|
||||
import { CodeFile } from "./models/code-file.js";
|
||||
|
||||
export class ApiView {
|
||||
name: string;
|
||||
packageName: string;
|
||||
packageVersion: string;
|
||||
crossLanguagePackageId: string | undefined;
|
||||
nodes: ApiTreeNode[] = [];
|
||||
diagnostics: ApiViewDiagnostic[] = [];
|
||||
versionString: string;
|
||||
diagnostics: CodeDiagnostic[] = [];
|
||||
|
||||
typeDeclarations = new Set<string>();
|
||||
includeGlobalNamespace: boolean;
|
||||
|
||||
constructor(name: string, packageName: string, versionString?: string, includeGlobalNamespace?: boolean) {
|
||||
constructor(name: string, packageName: string, packageVersion?: string, includeGlobalNamespace?: boolean) {
|
||||
this.name = name;
|
||||
this.packageName = packageName;
|
||||
this.versionString = versionString ?? "";
|
||||
this.packageVersion = packageVersion ?? "";
|
||||
this.includeGlobalNamespace = includeGlobalNamespace ?? false;
|
||||
this.crossLanguagePackageId = packageName;
|
||||
}
|
||||
|
@ -31,10 +32,11 @@ export class ApiView {
|
|||
* @param options options for node creation
|
||||
* @returns the created node
|
||||
*/
|
||||
node(parent: ApiTreeNode | ApiView, name: string, id: string, kind: NodeKind, options?: NodeOptions): ApiTreeNode {
|
||||
node(parent: ApiTreeNode | ApiView, name: string, id: string, kind: ApiTreeNodeKind, options?: ApiTreeNodeOptions) {
|
||||
const child = new ApiTreeNode(name, id, kind, {
|
||||
tags: options?.tags,
|
||||
properties: options?.properties,
|
||||
iconName: options?.iconName,
|
||||
crossLanguageDefinitionId: options?.crossLanguageDefinitionId,
|
||||
});
|
||||
if (parent instanceof ApiView) {
|
||||
parent.nodes.push(child);
|
||||
|
@ -43,7 +45,7 @@ export class ApiView {
|
|||
}
|
||||
}
|
||||
|
||||
child(parent: ApiTreeNode | ApiViewDocument, name: string, id: string, tags?: Tag[]) {
|
||||
child(parent: ApiTreeNode | CodeFile, name: string, id: string, tags?: Tag[]) {
|
||||
const child: ApiTreeNode = {
|
||||
_kind: "ApiTreeNode",
|
||||
name: name,
|
||||
|
@ -217,8 +219,8 @@ export class ApiView {
|
|||
});
|
||||
}
|
||||
|
||||
diagnostic(message: string, targetId: string, level: ApiViewDiagnosticLevel) {
|
||||
this.diagnostics.push(new ApiViewDiagnostic(message, targetId, level));
|
||||
diagnostic(message: string, targetId: string, level: CodeDiagnosticLevel) {
|
||||
this.diagnostics.push(new CodeDiagnostic(message, targetId, level));
|
||||
}
|
||||
|
||||
emit(program: Program) {
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
export class ApiViewDiagnostic {
|
||||
static idCounter: number = 1;
|
||||
diagnosticNumber: number;
|
||||
text: string;
|
||||
targetId: string;
|
||||
level: ApiViewDiagnosticLevel;
|
||||
helpLinkUri?: string;
|
||||
|
||||
constructor(message: string, targetId: string, level: ApiViewDiagnosticLevel) {
|
||||
this.diagnosticNumber = ApiViewDiagnostic.idCounter;
|
||||
ApiViewDiagnostic.idCounter++;
|
||||
this.text = message;
|
||||
this.targetId = targetId;
|
||||
this.level = level;
|
||||
}
|
||||
}
|
||||
|
||||
export enum ApiViewDiagnosticLevel {
|
||||
Default = 0,
|
||||
Info = 1,
|
||||
Warning = 2,
|
||||
Error = 3,
|
||||
}
|
|
@ -15,7 +15,7 @@ import { buildVersionProjections, getVersion } from "@typespec/versioning";
|
|||
import path from "path";
|
||||
import { ApiView } from "./apiview.js";
|
||||
import { ApiViewEmitterOptions, reportDiagnostic } from "./lib.js";
|
||||
import { ApiViewDocument } from "./apiview-document.js";
|
||||
import { CodeFile } from "./models/code-file.js";
|
||||
|
||||
export interface ResolvedApiViewEmitterOptions {
|
||||
emitterOutputDir: string;
|
||||
|
@ -194,9 +194,10 @@ function createApiViewEmitter(program: Program, options: ResolvedApiViewEmitterO
|
|||
await program.host.mkdirp(outputFolder);
|
||||
const outputFile = options.outputFile ?? `${namespaceString}-apiview.json`;
|
||||
const outputPath = resolvePath(outputFolder, outputFile);
|
||||
const serialized = new CodeFile(apiview).serialize();
|
||||
await emitFile(program, {
|
||||
path: outputPath,
|
||||
content: `${new ApiViewDocument(apiview).asString()}\n`,
|
||||
content: JSON.stringify(serialized, null, 2),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
/** Marks an object as being part of the ApiView server-side contract. */
|
||||
export interface ApiViewSerializable {
|
||||
/** Serializes the object to the format ApiView expects. */
|
||||
serialize(): object;
|
||||
}
|
|
@ -36,73 +36,112 @@ import {
|
|||
UnionVariantNode,
|
||||
ValueOfExpressionNode,
|
||||
} from "@typespec/compiler";
|
||||
import { RenderClass, StructuredToken, TokenKind, TokenLocation, TokenOptions, TokenTag } from "./structured-token.js";
|
||||
import { StructuredToken, StructuredTokenKind, StructuredTokenRenderClass, StructuredTokenTag, TokenLocation, TokenOptions } from "./structured-token.js";
|
||||
import {
|
||||
getFullyQualifiedIdentifier,
|
||||
definitionIdFor,
|
||||
getRawText,
|
||||
generateId,
|
||||
buildTemplateString,
|
||||
} from "./helpers.js";
|
||||
import { NamespaceModel } from "./namespace-model.js";
|
||||
import { ApiView } from "./apiview.js";
|
||||
} from "../helpers.js";
|
||||
import { NamespaceModel } from "../namespace-model.js";
|
||||
import { ApiView } from "../apiview.js";
|
||||
import { ApiViewSerializable } from "../interface.js";
|
||||
|
||||
const WHITESPACE = " ";
|
||||
|
||||
/** Tags supported by APIView v2 */
|
||||
export enum NodeTag {
|
||||
/** Show item as deprecated. */
|
||||
deprecated,
|
||||
/** Hide item from APIView. */
|
||||
hidden,
|
||||
/** Hide item from APIView Navigation. */
|
||||
hideFromNav,
|
||||
/** Ignore differences in this item when calculating diffs. */
|
||||
skipDiff,
|
||||
}
|
||||
|
||||
/** The kind values APIView supports for ApiTreeNodes. */
|
||||
export enum NodeKind {
|
||||
assembly,
|
||||
class,
|
||||
delegate,
|
||||
enum,
|
||||
interface,
|
||||
method,
|
||||
namespace,
|
||||
package,
|
||||
struct,
|
||||
type,
|
||||
export enum ApiTreeNodeKind {
|
||||
assembly = "assembly",
|
||||
class = "class",
|
||||
delegate = "delegate",
|
||||
enum = "enum",
|
||||
interface = "interface",
|
||||
method = "method",
|
||||
namespace = "namespace",
|
||||
package = "package",
|
||||
struct = "struct",
|
||||
type = "type",
|
||||
}
|
||||
|
||||
/** Options when creating a new ApiTreeNode. */
|
||||
export interface NodeOptions {
|
||||
tags?: NodeTag[];
|
||||
properties?: Map<string, string>;
|
||||
/**
|
||||
* Options when creating a new ApiTreeNode.
|
||||
*/
|
||||
export interface ApiTreeNodeOptions {
|
||||
tags?: ApiTreeNodeTag[];
|
||||
iconName?: string;
|
||||
crossLanguageDefinitionId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes optional properties settable on an ApiTreeNode.
|
||||
*/
|
||||
class ApiTreeNodeProperties implements ApiViewSerializable {
|
||||
iconName?: string;
|
||||
crossLanguageDefinitionId?: string;
|
||||
|
||||
constructor(iconName?: string, crossLanguageDefinitionId?: string) {
|
||||
this.iconName = iconName;
|
||||
this.crossLanguageDefinitionId = crossLanguageDefinitionId;
|
||||
}
|
||||
|
||||
serialize(): object {
|
||||
return {
|
||||
IconName: this.iconName,
|
||||
CrossLangDefId: this.crossLanguageDefinitionId,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tags supported by APIView v2 ApiTreeNode.
|
||||
*/
|
||||
enum ApiTreeNodeTag {
|
||||
/** Mark a token as deprecated. */
|
||||
deprecated = "Deprecated",
|
||||
/** Mark a node as hidden. */
|
||||
hidden = "Hidden",
|
||||
/** Indicate that a node should be omitted from the page navigation. */
|
||||
hideFromNav = "HideFromNav",
|
||||
/** Indicate that a node should not be used when computing a diff. */
|
||||
skipDiff = "SkipDiff",
|
||||
}
|
||||
|
||||
/** New-style structured APIView node. */
|
||||
export class ApiTreeNode {
|
||||
export class ApiTreeNode implements ApiViewSerializable {
|
||||
name: string;
|
||||
id: string;
|
||||
kind: NodeKind;
|
||||
tags?: Set<string>;
|
||||
properties: Map<string, string>;
|
||||
kind: ApiTreeNodeKind;
|
||||
topTokens: StructuredToken[];
|
||||
bottomTokens: StructuredToken[];
|
||||
children: ApiTreeNode[];
|
||||
properties?: ApiTreeNodeProperties;
|
||||
tags?: ApiTreeNodeTag[];
|
||||
|
||||
constructor(name: string, id: string, kind: NodeKind, options?: NodeOptions) {
|
||||
constructor(name: string, id: string, kind: ApiTreeNodeKind, options?: ApiTreeNodeOptions) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this.kind = kind;
|
||||
this.tags = new Set([...(options?.tags ?? []).toString()]);
|
||||
this.properties = options?.properties ?? new Map<string, string>();
|
||||
this.tags = options?.tags;
|
||||
this.properties = new ApiTreeNodeProperties(options?.iconName, options?.crossLanguageDefinitionId);
|
||||
this.topTokens = [];
|
||||
this.bottomTokens = [];
|
||||
this.children = [];
|
||||
}
|
||||
|
||||
serialize(): object {
|
||||
return {
|
||||
Name: this.name,
|
||||
Id: this.id,
|
||||
Kind: this.kind,
|
||||
TopTokens: this.topTokens?.map((t) => t.serialize()),
|
||||
BottomTokens: this.bottomTokens?.map((t) => t.serialize()),
|
||||
Children: this.children?.map((c) => c.serialize()),
|
||||
Properties: this.properties?.serialize(),
|
||||
Tags: this.tags
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new node on the parent.
|
||||
* @param parent the parent, which can be APIView itself for top-level nodes or another ApiTreeNode
|
||||
|
@ -111,14 +150,18 @@ export class ApiTreeNode {
|
|||
* @param options options for node creation
|
||||
* @returns the created node
|
||||
*/
|
||||
node(parent: ApiTreeNode | ApiView, name: string, id: string, kind: NodeKind, options?: NodeOptions): ApiTreeNode {
|
||||
node(parent: ApiTreeNode | ApiView, name: string, id: string, kind: ApiTreeNodeKind, options?: ApiTreeNodeOptions): ApiTreeNode {
|
||||
const child = new ApiTreeNode(name, id, kind, {
|
||||
tags: options?.tags,
|
||||
properties: options?.properties,
|
||||
iconName: options?.iconName,
|
||||
crossLanguageDefinitionId: options?.crossLanguageDefinitionId,
|
||||
});
|
||||
if (parent instanceof ApiView) {
|
||||
parent.nodes.push(child);
|
||||
} else {
|
||||
if (parent.children === undefined) {
|
||||
parent.children = [];
|
||||
}
|
||||
parent.children.push(child);
|
||||
}
|
||||
return child;
|
||||
|
@ -130,15 +173,8 @@ export class ApiTreeNode {
|
|||
* @param lineId an optional line id
|
||||
* @param options options you can set
|
||||
*/
|
||||
token(kind: TokenKind, options?: TokenOptions) {
|
||||
const token: StructuredToken = {
|
||||
kind: kind,
|
||||
value: options?.value,
|
||||
id: options?.lineId ?? "",
|
||||
tags: new Set([...(options?.tags ?? []).toString()]),
|
||||
properties: options?.properties ?? new Map<string, string>(),
|
||||
renderClasses: new Set([...(options?.renderClasses ?? []).toString()]),
|
||||
};
|
||||
token(kind: StructuredTokenKind, options?: TokenOptions) {
|
||||
const token = new StructuredToken(kind, options);
|
||||
const location = options?.location ?? TokenLocation.top;
|
||||
if (location === TokenLocation.top) {
|
||||
this.topTokens.push(token);
|
||||
|
@ -152,15 +188,15 @@ export class ApiTreeNode {
|
|||
* @param count number of spaces to add
|
||||
*/
|
||||
whitespace(count: number = 1) {
|
||||
this.topTokens.push(new StructuredToken(TokenKind.nonBreakingSpace, { value: WHITESPACE.repeat(count) }));
|
||||
this.topTokens.push(new StructuredToken(StructuredTokenKind.nonBreakingSpace, { value: WHITESPACE.repeat(count) }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures exactly one space.
|
||||
*/
|
||||
space() {
|
||||
if (this.topTokens[this.topTokens.length - 1]?.kind !== TokenKind.nonBreakingSpace) {
|
||||
this.topTokens.push(new StructuredToken(TokenKind.nonBreakingSpace, { value: WHITESPACE }));
|
||||
if (this.topTokens[this.topTokens.length - 1]?.kind !== StructuredTokenKind.nonBreakingSpace) {
|
||||
this.topTokens.push(new StructuredToken(StructuredTokenKind.nonBreakingSpace, { value: WHITESPACE }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,7 +204,7 @@ export class ApiTreeNode {
|
|||
* Adds a newline token.
|
||||
*/
|
||||
newline() {
|
||||
this.topTokens.push(new StructuredToken(TokenKind.lineBreak));
|
||||
this.topTokens.push(new StructuredToken(StructuredTokenKind.lineBreak));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -177,7 +213,7 @@ export class ApiTreeNode {
|
|||
*/
|
||||
blankLines(count: number) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
this.token(TokenKind.lineBreak, { tags: [TokenTag.skipDiff], location: TokenLocation.bottom });
|
||||
this.token(StructuredTokenKind.lineBreak, { tags: [StructuredTokenTag.skipDiff], location: TokenLocation.bottom });
|
||||
}
|
||||
// TODO: Erase this is no longer needed.
|
||||
// // count the number of trailing newlines (ignoring indent whitespace)
|
||||
|
@ -226,9 +262,9 @@ export class ApiTreeNode {
|
|||
if (prefixSpace) {
|
||||
this.space();
|
||||
}
|
||||
this.token(TokenKind.content, {
|
||||
this.token(StructuredTokenKind.content, {
|
||||
value: value,
|
||||
renderClasses: [RenderClass.punctuation],
|
||||
renderClasses: [StructuredTokenRenderClass.punctuation],
|
||||
location: location,
|
||||
});
|
||||
if (postfixSpace) {
|
||||
|
@ -241,9 +277,9 @@ export class ApiTreeNode {
|
|||
* @param text to render
|
||||
*/
|
||||
text(text: string) {
|
||||
this.token(TokenKind.content, {
|
||||
this.token(StructuredTokenKind.content, {
|
||||
value: text,
|
||||
renderClasses: [RenderClass.text],
|
||||
renderClasses: [StructuredTokenRenderClass.text],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -260,9 +296,9 @@ export class ApiTreeNode {
|
|||
if (prefixSpace) {
|
||||
this.space();
|
||||
}
|
||||
this.token(TokenKind.content, {
|
||||
this.token(StructuredTokenKind.content, {
|
||||
value: keyword,
|
||||
renderClasses: [RenderClass.keyword],
|
||||
renderClasses: [StructuredTokenRenderClass.keyword],
|
||||
});
|
||||
if (postfixSpace) {
|
||||
this.space();
|
||||
|
@ -282,9 +318,9 @@ export class ApiTreeNode {
|
|||
}
|
||||
this.typeDeclarations.add(typeId);
|
||||
}
|
||||
this.token(TokenKind.content, {
|
||||
this.token(StructuredTokenKind.content, {
|
||||
value: typeName,
|
||||
renderClasses: [RenderClass.typeName],
|
||||
renderClasses: [StructuredTokenRenderClass.typeName],
|
||||
lineId: typeId,
|
||||
});
|
||||
}
|
||||
|
@ -295,9 +331,9 @@ export class ApiTreeNode {
|
|||
* @param targetId the id of the target type for clickable linking
|
||||
*/
|
||||
typeReference(typeName: string, targetId?: string) {
|
||||
this.token(TokenKind.content, {
|
||||
this.token(StructuredTokenKind.content, {
|
||||
value: typeName,
|
||||
renderClasses: [RenderClass.typeName],
|
||||
renderClasses: [StructuredTokenRenderClass.typeName],
|
||||
lineId: targetId,
|
||||
});
|
||||
}
|
||||
|
@ -307,9 +343,9 @@ export class ApiTreeNode {
|
|||
* @param name to render
|
||||
*/
|
||||
member(name: string) {
|
||||
this.token(TokenKind.content, {
|
||||
this.token(StructuredTokenKind.content, {
|
||||
value: name,
|
||||
renderClasses: [RenderClass.memberName],
|
||||
renderClasses: [StructuredTokenRenderClass.memberName],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -320,9 +356,9 @@ export class ApiTreeNode {
|
|||
stringLiteral(value: string) {
|
||||
const lines = value.split("\n");
|
||||
if (lines.length === 1) {
|
||||
this.token(TokenKind.content, {
|
||||
this.token(StructuredTokenKind.content, {
|
||||
value: `\u0022${value}\u0022`,
|
||||
renderClasses: [RenderClass.stringLiteral],
|
||||
renderClasses: [StructuredTokenRenderClass.stringLiteral],
|
||||
});
|
||||
} else {
|
||||
this.punctuation(`"""`);
|
||||
|
@ -341,8 +377,8 @@ export class ApiTreeNode {
|
|||
*/
|
||||
literal(value: string) {
|
||||
this.topTokens.push(
|
||||
new StructuredToken(TokenKind.content, {
|
||||
renderClasses: [RenderClass.literal],
|
||||
new StructuredToken(StructuredTokenKind.content, {
|
||||
renderClasses: [StructuredTokenRenderClass.literal],
|
||||
value: value,
|
||||
}),
|
||||
);
|
||||
|
@ -357,7 +393,7 @@ export class ApiTreeNode {
|
|||
case SyntaxKind.AliasStatement:
|
||||
obj = node as AliasStatementNode;
|
||||
const aliasId = obj.id.sv;
|
||||
const aliasNode = this.node(this, aliasId, aliasId, NodeKind.type);
|
||||
const aliasNode = this.node(this, aliasId, aliasId, ApiTreeNodeKind.type);
|
||||
aliasNode.keyword("alias", { postfixSpace: true });
|
||||
aliasNode.typeDeclaration(aliasId, aliasId, true);
|
||||
aliasNode.tokenizeTemplateParameters(obj.templateParameters);
|
||||
|
@ -373,7 +409,7 @@ export class ApiTreeNode {
|
|||
obj = node as AugmentDecoratorStatementNode;
|
||||
const decoratorName = this.getNameForNode(obj.target);
|
||||
const decoratorNode = this.node(this, decoratorName, decoratorName, "decorator", {
|
||||
tags: [NodeTag.hideFromNav],
|
||||
tags: [ApiTreeNodeTag.hideFromNav],
|
||||
});
|
||||
decoratorNode.punctuation("@@");
|
||||
decoratorNode.tokenizeIdentifier(obj.target, "keyword");
|
||||
|
@ -422,7 +458,7 @@ export class ApiTreeNode {
|
|||
case SyntaxKind.DirectiveExpression:
|
||||
obj = node as DirectiveExpressionNode;
|
||||
const nodeId = generateId(node)!;
|
||||
const directiveNode = this.node(this, nodeId, nodeId, "directive", { tags: [NodeTag.hideFromNav] });
|
||||
const directiveNode = this.node(this, nodeId, nodeId, "directive", { tags: [ApiTreeNodeTag.hideFromNav] });
|
||||
directiveNode.keyword(`#${obj.target.sv}`, { postfixSpace: true });
|
||||
for (const arg of obj.arguments) {
|
||||
switch (arg.kind) {
|
||||
|
@ -734,7 +770,7 @@ export class ApiTreeNode {
|
|||
|
||||
private tokenizeModelStatement(node: ModelStatementNode) {
|
||||
const nodeId = node.id.sv;
|
||||
const modelNode = this.node(this, nodeId, nodeId, NodeKind.class);
|
||||
const modelNode = this.node(this, nodeId, nodeId, ApiTreeNodeKind.class);
|
||||
modelNode.tokenizeDecoratorsAndDirectives(node.decorators, node.directives, false);
|
||||
modelNode.keyword("model", { postfixSpace: true });
|
||||
modelNode.tokenizeIdentifier(node.id, "declaration");
|
||||
|
@ -762,7 +798,7 @@ export class ApiTreeNode {
|
|||
|
||||
private tokenizeScalarStatement(node: ScalarStatementNode) {
|
||||
const nodeId = node.id.sv;
|
||||
const scalarNode = this.node(this, nodeId, nodeId, NodeKind.type);
|
||||
const scalarNode = this.node(this, nodeId, nodeId, ApiTreeNodeKind.type);
|
||||
scalarNode.tokenizeDecoratorsAndDirectives(node.decorators, node.directives, false);
|
||||
scalarNode.keyword("scalar", { postfixSpace: true });
|
||||
scalarNode.tokenizeIdentifier(node.id, "declaration");
|
||||
|
@ -776,7 +812,7 @@ export class ApiTreeNode {
|
|||
|
||||
private tokenizeInterfaceStatement(node: InterfaceStatementNode) {
|
||||
const nodeId = node.id.sv;
|
||||
const interfaceNode = this.node(this, nodeId, nodeId, NodeKind.interface);
|
||||
const interfaceNode = this.node(this, nodeId, nodeId, ApiTreeNodeKind.interface);
|
||||
interfaceNode.tokenizeDecoratorsAndDirectives(node.decorators, node.directives, false);
|
||||
interfaceNode.keyword("interface", { postfixSpace: true });
|
||||
interfaceNode.tokenizeIdentifier(node.id, "declaration");
|
||||
|
@ -784,7 +820,7 @@ export class ApiTreeNode {
|
|||
for (let x = 0; x < node.operations.length; x++) {
|
||||
const op = node.operations[x];
|
||||
const opId = generateId(op)!;
|
||||
const opNode = interfaceNode.node(interfaceNode, opId, opId, NodeKind.method);
|
||||
const opNode = interfaceNode.node(interfaceNode, opId, opId, ApiTreeNodeKind.method);
|
||||
opNode.tokenizeOperationStatement(op, true);
|
||||
interfaceNode.blankLines(x !== node.operations.length - 1 ? 1 : 0);
|
||||
}
|
||||
|
@ -792,7 +828,7 @@ export class ApiTreeNode {
|
|||
|
||||
private tokenizeEnumStatement(node: EnumStatementNode) {
|
||||
const nodeId = node.id.sv;
|
||||
const enumNode = this.node(this, nodeId, nodeId, NodeKind.enum);
|
||||
const enumNode = this.node(this, nodeId, nodeId, ApiTreeNodeKind.enum);
|
||||
enumNode.tokenizeDecoratorsAndDirectives(node.decorators, node.directives, false);
|
||||
enumNode.keyword("enum", { postfixSpace: true });
|
||||
enumNode.tokenizeIdentifier(node.id, "declaration");
|
||||
|
@ -807,7 +843,7 @@ export class ApiTreeNode {
|
|||
|
||||
private tokenizeUnionStatement(node: UnionStatementNode) {
|
||||
const nodeId = node.id.sv;
|
||||
const unionNode = this.node(this, nodeId, nodeId, NodeKind.enum);
|
||||
const unionNode = this.node(this, nodeId, nodeId, ApiTreeNodeKind.enum);
|
||||
unionNode.tokenizeDecoratorsAndDirectives(node.decorators, node.directives, false);
|
||||
unionNode.keyword("union", { postfixSpace: true });
|
||||
unionNode.tokenizeIdentifier(node.id, "declaration");
|
||||
|
@ -877,7 +913,7 @@ export class ApiTreeNode {
|
|||
isOperationSignature: boolean,
|
||||
leadingNewline: boolean,
|
||||
) {
|
||||
const modelNode = this.node(this, "anonymous", "anonymous", NodeKind.class);
|
||||
const modelNode = this.node(this, "anonymous", "anonymous", ApiTreeNodeKind.class);
|
||||
if (node.properties.length) {
|
||||
if (leadingNewline) {
|
||||
modelNode.blankLines(0);
|
||||
|
@ -928,7 +964,7 @@ export class ApiTreeNode {
|
|||
|
||||
private tokenizeOperationStatement(node: OperationStatementNode, suppressOpKeyword: boolean = false) {
|
||||
const nodeId = node.id.sv;
|
||||
const opNode = this.node(this, nodeId, nodeId, NodeKind.method);
|
||||
const opNode = this.node(this, nodeId, nodeId, ApiTreeNodeKind.method);
|
||||
opNode.tokenizeDecoratorsAndDirectives(node.decorators, node.directives, false);
|
||||
if (!suppressOpKeyword) {
|
||||
opNode.keyword("op", { postfixSpace: true });
|
|
@ -0,0 +1,35 @@
|
|||
import { ApiViewSerializable } from "../interface.js";
|
||||
|
||||
export class CodeDiagnostic implements ApiViewSerializable{
|
||||
static idCounter: number = 1;
|
||||
diagnosticId: number;
|
||||
text: string;
|
||||
targetId: string;
|
||||
level: CodeDiagnosticLevel;
|
||||
helpLinkUri?: string;
|
||||
|
||||
constructor(message: string, targetId: string, level: CodeDiagnosticLevel) {
|
||||
this.diagnosticId = CodeDiagnostic.idCounter;
|
||||
CodeDiagnostic.idCounter++;
|
||||
this.text = message;
|
||||
this.targetId = targetId;
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
serialize(): object {
|
||||
return {
|
||||
DiagnosticId: this.diagnosticId,
|
||||
Text: this.text,
|
||||
HelpLinkUri: this.helpLinkUri,
|
||||
TargetId: this.targetId,
|
||||
Level: this.level,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export enum CodeDiagnosticLevel {
|
||||
Info = 1,
|
||||
Warning = 2,
|
||||
Error = 3,
|
||||
Fatal = 4,
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
import { ApiTreeNode } from "./api-tree-node.js";
|
||||
import { ApiView } from "../apiview.js";
|
||||
import { CodeDiagnostic } from "./code-diagnostic.js";
|
||||
import { LIB_VERSION } from "../version.js";
|
||||
import { ApiViewSerializable } from "../interface.js";
|
||||
|
||||
/**
|
||||
* The serializable form of the ApiView. This is the tokenfile that gets sent to the APIView service.
|
||||
*/
|
||||
export class CodeFile implements ApiViewSerializable {
|
||||
parserVersion: string;
|
||||
name: string;
|
||||
language: string;
|
||||
packageName: string;
|
||||
packageVersion: string;
|
||||
|
||||
crossLanguagePackageId: string | undefined;
|
||||
apiForest: ApiTreeNode[] | null;
|
||||
diagnostics: CodeDiagnostic[];
|
||||
|
||||
constructor(apiview: ApiView) {
|
||||
this.parserVersion = LIB_VERSION;
|
||||
this.name = apiview.name;
|
||||
this.packageName = apiview.packageName;
|
||||
this.packageVersion = apiview.packageVersion
|
||||
this.apiForest = apiview.nodes;
|
||||
this.diagnostics = apiview.diagnostics;
|
||||
this.language = "TypeSpec";
|
||||
this.crossLanguagePackageId = apiview.crossLanguagePackageId;
|
||||
}
|
||||
|
||||
asString(): string {
|
||||
return JSON.stringify(this, null, 2);
|
||||
}
|
||||
|
||||
serialize(): object {
|
||||
return {
|
||||
VersionString: this.parserVersion,
|
||||
Name: this.name,
|
||||
Language: "TypeSpec",
|
||||
LanguageVariant: null,
|
||||
PackageName: this.packageName,
|
||||
PackageVersion: this.packageVersion,
|
||||
ServiceName: null,
|
||||
CrossLanguagePackageId: this.crossLanguagePackageId,
|
||||
APIForest: this.apiForest?.map(node => node.serialize()),
|
||||
Diagnostics: this.diagnostics.map(diag => diag.serialize())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
import { ApiViewSerializable } from "../interface.js";
|
||||
|
||||
/**
|
||||
* Helper enum to declare where a token should be placed.
|
||||
*/
|
||||
export const enum TokenLocation {
|
||||
/** Apithis.TopTokens. Most tokens will go here. */
|
||||
top,
|
||||
/** Apithis.BottomTokens. Useful for closing braces. */
|
||||
bottom,
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the type of a structured token. Most tokens will be of type
|
||||
* content.
|
||||
*/
|
||||
export const enum StructuredTokenKind {
|
||||
content = 0,
|
||||
lineBreak = 1,
|
||||
nonBreakingSpace = 2,
|
||||
tabSpace = 3,
|
||||
parameterSeparator = 4,
|
||||
}
|
||||
|
||||
/**
|
||||
* Options when creating a new StructuredToken.
|
||||
*/
|
||||
export interface TokenOptions {
|
||||
renderClasses?: (StructuredTokenRenderClass | string)[];
|
||||
value?: string;
|
||||
tags?: StructuredTokenTag[];
|
||||
properties?: Map<string, string>;
|
||||
location?: TokenLocation;
|
||||
lineId?: string;
|
||||
groupId?: "doc" | undefined;
|
||||
navigateToId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* New-style structured APIView token.
|
||||
*/
|
||||
export class StructuredToken implements ApiViewSerializable {
|
||||
id?: string;
|
||||
kind: StructuredTokenKind;
|
||||
value?: string;
|
||||
properties?: StructuredTokenProperties;
|
||||
tags?: StructuredTokenTag[];
|
||||
renderClasses?: (StructuredTokenRenderClass | string)[];
|
||||
|
||||
constructor(kind: StructuredTokenKind, options?: TokenOptions) {
|
||||
this.id = options?.lineId;
|
||||
this.kind = kind;
|
||||
this.value = options?.value;
|
||||
this.properties = new StructuredTokenProperties(options?.groupId, options?.navigateToId);
|
||||
this.renderClasses = options?.renderClasses;
|
||||
}
|
||||
|
||||
serialize(): object {
|
||||
return {
|
||||
Id: this.id,
|
||||
Kind: this.kind,
|
||||
Value: this.value,
|
||||
Properties: this.properties?.serialize(),
|
||||
Tags: this.tags,
|
||||
RenderClasses: this.renderClasses,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties which can be set on a structured token.
|
||||
*/
|
||||
class StructuredTokenProperties implements ApiViewSerializable {
|
||||
groupId?: "doc" | undefined;
|
||||
navigateToId?: string;
|
||||
|
||||
constructor(groupId?: "doc" | undefined, navigateToId?: string) {
|
||||
this.groupId = groupId;
|
||||
this.navigateToId = navigateToId;
|
||||
}
|
||||
|
||||
serialize(): object {
|
||||
return {
|
||||
GroupId: this.groupId,
|
||||
NavigateToId: this.navigateToId,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tags which can be set on a structured token.
|
||||
*/
|
||||
export enum StructuredTokenTag {
|
||||
deprecated = "Deprecated",
|
||||
skipDiff = "SkipDiff"
|
||||
}
|
||||
|
||||
/**
|
||||
* Render classes applicable to a structured token.
|
||||
*/
|
||||
export enum StructuredTokenRenderClass {
|
||||
comment = "comment",
|
||||
keyword = "keyword",
|
||||
literal = "literal",
|
||||
stringLiteral = "sliteral",
|
||||
memberName = "mname",
|
||||
typeName = "tname",
|
||||
punctuation = "punc",
|
||||
text = "text",
|
||||
}
|
|
@ -19,8 +19,8 @@ import {
|
|||
} from "@typespec/compiler";
|
||||
import { caseInsensitiveSort, findNodes, generateId } from "./helpers.js";
|
||||
import { ApiView } from "./apiview.js";
|
||||
import { TokenLocation } from "./structured-token.js";
|
||||
import { NodeKind, NodeTag } from "./api-tree-node.js";
|
||||
import { TokenLocation } from "./models/structured-token.js";
|
||||
import { ApiTreeNodeKind } from "./models/api-tree-node.js";
|
||||
|
||||
export class NamespaceModel {
|
||||
kind = SyntaxKind.NamespaceStatement;
|
||||
|
@ -133,7 +133,7 @@ export class NamespaceModel {
|
|||
}
|
||||
|
||||
tokenize(apiview: ApiView) {
|
||||
const treeNode = apiview.node(apiview, this.name, this.name, NodeKind.namespace);
|
||||
const treeNode = apiview.node(apiview, this.name, this.name, ApiTreeNodeKind.namespace);
|
||||
|
||||
if (this.node.kind === SyntaxKind.NamespaceStatement) {
|
||||
treeNode.tokenizeDecoratorsAndDirectives(this.node.decorators, this.node.directives, false);
|
||||
|
@ -148,25 +148,25 @@ export class NamespaceModel {
|
|||
}
|
||||
for (const node of this.operations.values()) {
|
||||
const nodeId = generateId(node)!;
|
||||
const subNode = apiview.node(treeNode, nodeId, nodeId, NodeKind.method);
|
||||
const subNode = apiview.node(treeNode, nodeId, nodeId, ApiTreeNodeKind.method);
|
||||
subNode.tokenize(node);
|
||||
subNode.blankLines(1);
|
||||
}
|
||||
for (const node of this.resources.values()) {
|
||||
const nodeId = generateId(node)!;
|
||||
const subNode = apiview.node(treeNode, nodeId, nodeId, NodeKind.class);
|
||||
const subNode = apiview.node(treeNode, nodeId, nodeId, ApiTreeNodeKind.class);
|
||||
subNode.tokenize(node);
|
||||
subNode.blankLines(1);
|
||||
}
|
||||
for (const node of this.models.values()) {
|
||||
const nodeId = generateId(node)!;
|
||||
const subNode = apiview.node(treeNode, nodeId, nodeId, NodeKind.class);
|
||||
const subNode = apiview.node(treeNode, nodeId, nodeId, ApiTreeNodeKind.class);
|
||||
subNode.tokenize(node);
|
||||
subNode.blankLines(1);
|
||||
}
|
||||
for (const node of this.aliases.values()) {
|
||||
const nodeId = generateId(node)!;
|
||||
const subNode = apiview.node(treeNode, nodeId, nodeId, NodeKind.type);
|
||||
const subNode = apiview.node(treeNode, nodeId, nodeId, ApiTreeNodeKind.type);
|
||||
subNode.tokenize(node);
|
||||
subNode.punctuation(";");
|
||||
subNode.blankLines(1);
|
||||
|
|
|
@ -1,154 +0,0 @@
|
|||
import {
|
||||
AliasStatementNode,
|
||||
EnumStatementNode,
|
||||
InterfaceStatementNode,
|
||||
IntersectionExpressionNode,
|
||||
ModelExpressionNode,
|
||||
ModelStatementNode,
|
||||
ObjectLiteralNode,
|
||||
OperationStatementNode,
|
||||
ProjectionModelExpressionNode,
|
||||
ScalarStatementNode,
|
||||
SyntaxKind,
|
||||
UnionExpressionNode,
|
||||
UnionStatementNode,
|
||||
} from "@typespec/compiler";
|
||||
import { ApiView, NamespaceStack } from "./apiview.js";
|
||||
import { NamespaceModel } from "./namespace-model.js";
|
||||
|
||||
export class ApiViewNavigation {
|
||||
Text: string;
|
||||
NavigationId: string | undefined;
|
||||
ChildItems: ApiViewNavigation[];
|
||||
Tags: ApiViewNavigationTag;
|
||||
|
||||
constructor(
|
||||
objNode:
|
||||
| AliasStatementNode
|
||||
| NamespaceModel
|
||||
| ModelStatementNode
|
||||
| OperationStatementNode
|
||||
| InterfaceStatementNode
|
||||
| EnumStatementNode
|
||||
| ModelExpressionNode
|
||||
| IntersectionExpressionNode
|
||||
| ProjectionModelExpressionNode
|
||||
| ScalarStatementNode
|
||||
| UnionStatementNode
|
||||
| UnionExpressionNode
|
||||
| ObjectLiteralNode,
|
||||
stack: NamespaceStack
|
||||
) {
|
||||
let obj;
|
||||
switch (objNode.kind) {
|
||||
case SyntaxKind.NamespaceStatement:
|
||||
stack.push(objNode.name);
|
||||
this.Text = objNode.name;
|
||||
this.Tags = { TypeKind: ApiViewNavigationKind.Module };
|
||||
const operationItems = new Array<ApiViewNavigation>();
|
||||
for (const node of objNode.operations.values()) {
|
||||
operationItems.push(new ApiViewNavigation(node, stack));
|
||||
}
|
||||
const resourceItems = new Array<ApiViewNavigation>();
|
||||
for (const node of objNode.resources.values()) {
|
||||
resourceItems.push(new ApiViewNavigation(node, stack));
|
||||
}
|
||||
const modelItems = new Array<ApiViewNavigation>();
|
||||
for (const node of objNode.models.values()) {
|
||||
modelItems.push(new ApiViewNavigation(node, stack));
|
||||
}
|
||||
const aliasItems = new Array<ApiViewNavigation>();
|
||||
for (const node of objNode.aliases.values()) {
|
||||
aliasItems.push(new ApiViewNavigation(node, stack));
|
||||
}
|
||||
this.ChildItems = [];
|
||||
if (operationItems.length) {
|
||||
this.ChildItems.push({ Text: "Operations", ChildItems: operationItems, Tags: { TypeKind: ApiViewNavigationKind.Method }, NavigationId: "" });
|
||||
}
|
||||
if (resourceItems.length) {
|
||||
this.ChildItems.push({ Text: "Resources", ChildItems: resourceItems, Tags: { TypeKind: ApiViewNavigationKind.Class }, NavigationId: "" });
|
||||
}
|
||||
if (modelItems.length) {
|
||||
this.ChildItems.push({ Text: "Models", ChildItems: modelItems, Tags: { TypeKind: ApiViewNavigationKind.Class }, NavigationId: "" });
|
||||
}
|
||||
if (aliasItems.length) {
|
||||
this.ChildItems.push({ Text: "Aliases", ChildItems: aliasItems, Tags: { TypeKind: ApiViewNavigationKind.Class }, NavigationId: "" });
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.ModelStatement:
|
||||
obj = objNode as ModelStatementNode;
|
||||
stack.push(obj.id.sv);
|
||||
this.Text = obj.id.sv;
|
||||
this.Tags = { TypeKind: ApiViewNavigationKind.Class };
|
||||
this.ChildItems = [];
|
||||
break;
|
||||
case SyntaxKind.EnumStatement:
|
||||
obj = objNode as EnumStatementNode;
|
||||
stack.push(obj.id.sv);
|
||||
this.Text = obj.id.sv;
|
||||
this.Tags = { TypeKind: ApiViewNavigationKind.Enum };
|
||||
this.ChildItems = [];
|
||||
break;
|
||||
case SyntaxKind.OperationStatement:
|
||||
obj = objNode as OperationStatementNode;
|
||||
stack.push(obj.id.sv);
|
||||
this.Text = obj.id.sv;
|
||||
this.Tags = { TypeKind: ApiViewNavigationKind.Method };
|
||||
this.ChildItems = [];
|
||||
break;
|
||||
case SyntaxKind.InterfaceStatement:
|
||||
obj = objNode as InterfaceStatementNode;
|
||||
stack.push(obj.id.sv);
|
||||
this.Text = obj.id.sv;
|
||||
this.Tags = { TypeKind: ApiViewNavigationKind.Method };
|
||||
this.ChildItems = [];
|
||||
for (const child of obj.operations) {
|
||||
this.ChildItems.push(new ApiViewNavigation(child, stack));
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.UnionStatement:
|
||||
obj = objNode as UnionStatementNode;
|
||||
stack.push(obj.id.sv);
|
||||
this.Text = obj.id.sv;
|
||||
this.Tags = { TypeKind: ApiViewNavigationKind.Enum };
|
||||
this.ChildItems = [];
|
||||
break;
|
||||
case SyntaxKind.AliasStatement:
|
||||
obj = objNode as AliasStatementNode;
|
||||
stack.push(obj.id.sv);
|
||||
this.Text = obj.id.sv;
|
||||
this.Tags = { TypeKind: ApiViewNavigationKind.Class };
|
||||
this.ChildItems = [];
|
||||
break;
|
||||
case SyntaxKind.ModelExpression:
|
||||
throw new Error(`Navigation unsupported for "ModelExpression".`);
|
||||
case SyntaxKind.IntersectionExpression:
|
||||
throw new Error(`Navigation unsupported for "IntersectionExpression".`);
|
||||
case SyntaxKind.ProjectionModelExpression:
|
||||
throw new Error(`Navigation unsupported for "ProjectionModelExpression".`);
|
||||
case SyntaxKind.ScalarStatement:
|
||||
obj = objNode as ScalarStatementNode;
|
||||
stack.push(obj.id.sv);
|
||||
this.Text = obj.id.sv;
|
||||
this.Tags = { TypeKind: ApiViewNavigationKind.Class };
|
||||
this.ChildItems = [];
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Navigation unsupported for "${objNode.kind.toString()}".`);
|
||||
}
|
||||
this.NavigationId = stack.value();
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
export interface ApiViewNavigationTag {
|
||||
TypeKind: ApiViewNavigationKind;
|
||||
}
|
||||
|
||||
export const enum ApiViewNavigationKind {
|
||||
Class = "class",
|
||||
Enum = "enum",
|
||||
Method = "method",
|
||||
Module = "namespace",
|
||||
Package = "assembly",
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
/** Supported render classes for APIView v2.
|
||||
* You can add custom ones but need to provide CSS to EngSys.
|
||||
*/
|
||||
export const enum RenderClass {
|
||||
text,
|
||||
keyword,
|
||||
punctuation,
|
||||
literal,
|
||||
comment,
|
||||
typeName = "type-name",
|
||||
memberName = "member-name",
|
||||
stringLiteral = "string-literal",
|
||||
}
|
||||
|
||||
/** Tags supported by APIView v2 */
|
||||
export enum TokenTag {
|
||||
/** Show item as deprecated. */
|
||||
deprecated,
|
||||
/** Hide item from APIView. */
|
||||
hidden,
|
||||
/** Hide item from APIView Navigation. */
|
||||
hideFromNav,
|
||||
/** Ignore differences in this item when calculating diffs. */
|
||||
skipDiff,
|
||||
}
|
||||
|
||||
export const enum TokenLocation {
|
||||
/** Apithis.TopTokens. Most tokens will go here. */
|
||||
top,
|
||||
/** Apithis.BottomTokens. Useful for closing braces. */
|
||||
bottom,
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the type of structured token.
|
||||
*/
|
||||
export const enum TokenKind {
|
||||
content = 0,
|
||||
lineBreak = 1,
|
||||
nonBreakingSpace = 2,
|
||||
tabSpace = 3,
|
||||
parameterSeparator = 4,
|
||||
url = 5,
|
||||
}
|
||||
|
||||
/**
|
||||
* Options when creating a new StructuredToken.
|
||||
*/
|
||||
export interface TokenOptions {
|
||||
renderClasses?: RenderClass[];
|
||||
value?: string;
|
||||
tags?: TokenTag[];
|
||||
properties?: Map<string, string>;
|
||||
location?: TokenLocation;
|
||||
lineId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* New-style structured APIView token.
|
||||
*/
|
||||
export class StructuredToken {
|
||||
value?: string;
|
||||
id?: string;
|
||||
kind: TokenKind;
|
||||
tags?: Set<string>;
|
||||
properties: Map<string, string>;
|
||||
renderClasses: Set<string>;
|
||||
|
||||
constructor(kind: TokenKind, options?: TokenOptions) {
|
||||
this.id = options?.lineId;
|
||||
this.kind = kind;
|
||||
this.value = options?.value;
|
||||
this.properties = options?.properties ?? new Map<string, string>();
|
||||
this.renderClasses = new Set([...(options?.renderClasses ?? []).toString()]);
|
||||
}
|
||||
}
|
|
@ -1 +1 @@
|
|||
export const LIB_VERSION = "0.4.9";
|
||||
export const LIB_VERSION = "0.5.0";
|
||||
|
|
Загрузка…
Ссылка в новой задаче