mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Automatically generated Api documentation (#86232)
* auto generated mdx api doc system * Fix README.md * update core api docs after master merge * clean up signature * Update packages/kbn-dev-utils/src/plugins/parse_kibana_platform_plugin.ts Co-authored-by: Spencer <email@spalger.com> * migrate to docs-util package * Remove bad links * fix ref to release-notes and add extra dats service folder * update name change * Update packages/kbn-docs-utils/src/api_docs/build_api_declarations/get_type_kind.ts Co-authored-by: Brandon Kobel <brandon.kobel@gmail.com> * Update packages/kbn-docs-utils/src/api_docs/utils.ts Co-authored-by: Brandon Kobel <brandon.kobel@gmail.com> * review updates 1 * review feedback updates round 1 * Small refactor of extractImportReferences * Review feedback updates 2 * Review update 3 plus support for links in class interface heritage clause * debug failing test on ci only * Escape regex directory path * Update packages/kbn-docs-utils/src/api_docs/build_api_declarations/utils.ts Co-authored-by: Spencer <email@spalger.com> * fix for commit suggestion Co-authored-by: Spencer <email@spalger.com> Co-authored-by: spalger <spalger@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Brandon Kobel <brandon.kobel@gmail.com> Co-authored-by: kobelb <brandon.kobel@elastic.co>
This commit is contained in:
parent
91d03f0c45
commit
deb555a552
85 changed files with 4156 additions and 620 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
|||
.aws-config.json
|
||||
.signing-config.json
|
||||
/api_docs
|
||||
.ackrc
|
||||
/.es
|
||||
/.chromium
|
||||
|
|
|
@ -28,6 +28,7 @@ Should never be used in code outside of Core but is exported for documentation p
|
|||
| [requiredBundles](./kibana-plugin-core-server.pluginmanifest.requiredbundles.md) | <code>readonly string[]</code> | List of plugin ids that this plugin's UI code imports modules from that are not in <code>requiredPlugins</code>. |
|
||||
| [requiredPlugins](./kibana-plugin-core-server.pluginmanifest.requiredplugins.md) | <code>readonly PluginName[]</code> | An optional list of the other plugins that \*\*must be\*\* installed and enabled for this plugin to function properly. |
|
||||
| [server](./kibana-plugin-core-server.pluginmanifest.server.md) | <code>boolean</code> | Specifies whether plugin includes some server-side specific functionality. |
|
||||
| [serviceFolders](./kibana-plugin-core-server.pluginmanifest.servicefolders.md) | <code>readonly string[]</code> | Only used for the automatically generated API documentation. Specifying service folders will cause your plugin API reference to be broken up into sub sections. |
|
||||
| [ui](./kibana-plugin-core-server.pluginmanifest.ui.md) | <code>boolean</code> | Specifies whether plugin includes some client/browser specific functionality that should be included into client bundle via <code>public/ui_plugin.js</code> file. |
|
||||
| [version](./kibana-plugin-core-server.pluginmanifest.version.md) | <code>string</code> | Version of the plugin. |
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PluginManifest](./kibana-plugin-core-server.pluginmanifest.md) > [serviceFolders](./kibana-plugin-core-server.pluginmanifest.servicefolders.md)
|
||||
|
||||
## PluginManifest.serviceFolders property
|
||||
|
||||
Only used for the automatically generated API documentation. Specifying service folders will cause your plugin API reference to be broken up into sub sections.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly serviceFolders?: readonly string[];
|
||||
```
|
|
@ -47,6 +47,7 @@
|
|||
"test:ftr:runner": "node scripts/functional_test_runner",
|
||||
"checkLicenses": "node scripts/check_licenses --dev",
|
||||
"build": "node scripts/build --all-platforms",
|
||||
"build:apidocs": "node scripts/build_api_docs",
|
||||
"start": "node scripts/kibana --dev",
|
||||
"debug": "node --nolazy --inspect scripts/kibana --dev",
|
||||
"debug-break": "node --nolazy --inspect-brk scripts/kibana --dev",
|
||||
|
@ -367,7 +368,7 @@
|
|||
"@kbn/plugin-generator": "link:packages/kbn-plugin-generator",
|
||||
"@kbn/plugin-helpers": "link:packages/kbn-plugin-helpers",
|
||||
"@kbn/pm": "link:packages/kbn-pm",
|
||||
"@kbn/release-notes": "link:packages/kbn-release-notes",
|
||||
"@kbn/docs-utils": "link:packages/kbn-docs-utils",
|
||||
"@kbn/storybook": "link:packages/kbn-storybook",
|
||||
"@kbn/telemetry-tools": "link:packages/kbn-telemetry-tools",
|
||||
"@kbn/test": "link:packages/kbn-test",
|
||||
|
@ -821,6 +822,7 @@
|
|||
"tinycolor2": "1.4.1",
|
||||
"topojson-client": "3.0.0",
|
||||
"ts-loader": "^7.0.5",
|
||||
"ts-morph": "^9.1.0",
|
||||
"tsd": "^0.13.1",
|
||||
"typescript": "4.1.3",
|
||||
"typescript-fsa": "^3.0.0",
|
||||
|
|
|
@ -29,6 +29,7 @@ interface Manifest {
|
|||
server: boolean;
|
||||
kibanaVersion: string;
|
||||
version: string;
|
||||
serviceFolders: readonly string[];
|
||||
requiredPlugins: readonly string[];
|
||||
optionalPlugins: readonly string[];
|
||||
requiredBundles: readonly string[];
|
||||
|
@ -64,6 +65,7 @@ export function parseKibanaPlatformPlugin(manifestPath: string): KibanaPlatformP
|
|||
id: manifest.id,
|
||||
version: manifest.version,
|
||||
kibanaVersion: manifest.kibanaVersion || manifest.version,
|
||||
serviceFolders: manifest.serviceFolders || [],
|
||||
requiredPlugins: isValidDepsDeclaration(manifest.requiredPlugins, 'requiredPlugins'),
|
||||
optionalPlugins: isValidDepsDeclaration(manifest.optionalPlugins, 'optionalPlugins'),
|
||||
requiredBundles: isValidDepsDeclaration(manifest.requiredBundles, 'requiredBundles'),
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-release-notes'],
|
||||
roots: ['<rootDir>/packages/kbn-docs-utils'],
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "@kbn/release-notes",
|
||||
"name": "@kbn/docs-utils",
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0",
|
||||
"private": "true",
|
12
packages/kbn-docs-utils/src/api_docs/README.md
Normal file
12
packages/kbn-docs-utils/src/api_docs/README.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Autogenerated API documentation
|
||||
|
||||
[RFC](../../../rfcs/text/0014_api_documentation.md)
|
||||
|
||||
This is an experimental api documentation system that is managed by the Kibana Tech Leads until
|
||||
we determine the value of such a system and what kind of maintenance burder it will incur.
|
||||
|
||||
To generate the docs run
|
||||
|
||||
```
|
||||
node scripts/build_api_docs
|
||||
```
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import Path from 'path';
|
||||
import { Project, Node } from 'ts-morph';
|
||||
import { ToolingLog, KibanaPlatformPlugin } from '@kbn/dev-utils';
|
||||
|
||||
import { TypeKind, ApiScope } from '../types';
|
||||
import { getKibanaPlatformPlugin } from '../tests/kibana_platform_plugin_mock';
|
||||
import { getDeclarationNodesForPluginScope } from '../get_declaration_nodes_for_plugin';
|
||||
import { buildApiDeclaration } from './build_api_declaration';
|
||||
import { isNamedNode } from '../tsmorph_utils';
|
||||
|
||||
const log = new ToolingLog({
|
||||
level: 'debug',
|
||||
writeTo: process.stdout,
|
||||
});
|
||||
|
||||
let nodes: Node[];
|
||||
let plugins: KibanaPlatformPlugin[];
|
||||
|
||||
function getNodeName(node: Node): string {
|
||||
return isNamedNode(node) ? node.getName() : '';
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
const tsConfigFilePath = Path.resolve(__dirname, '../tests/__fixtures__/src/tsconfig.json');
|
||||
const project = new Project({
|
||||
tsConfigFilePath,
|
||||
});
|
||||
|
||||
plugins = [getKibanaPlatformPlugin('pluginA')];
|
||||
|
||||
nodes = getDeclarationNodesForPluginScope(project, plugins[0], ApiScope.CLIENT, log);
|
||||
});
|
||||
|
||||
it('Test number primitive doc def', () => {
|
||||
const node = nodes.find((n) => getNodeName(n) === 'aNum');
|
||||
expect(node).toBeDefined();
|
||||
const def = buildApiDeclaration(node!, plugins, log, plugins[0].manifest.id, ApiScope.CLIENT);
|
||||
|
||||
expect(def.type).toBe(TypeKind.NumberKind);
|
||||
});
|
||||
|
||||
it('Function type is exported as type with signature', () => {
|
||||
const node = nodes.find((n) => getNodeName(n) === 'FnWithGeneric');
|
||||
expect(node).toBeDefined();
|
||||
const def = buildApiDeclaration(node!, plugins, log, plugins[0].manifest.id, ApiScope.CLIENT);
|
||||
expect(def).toBeDefined();
|
||||
expect(def?.type).toBe(TypeKind.TypeKind);
|
||||
expect(def?.signature?.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('Test Interface Kind doc def', () => {
|
||||
const node = nodes.find((n) => getNodeName(n) === 'ExampleInterface');
|
||||
expect(node).toBeDefined();
|
||||
const def = buildApiDeclaration(node!, plugins, log, plugins[0].manifest.id, ApiScope.CLIENT);
|
||||
|
||||
expect(def.type).toBe(TypeKind.InterfaceKind);
|
||||
expect(def.children).toBeDefined();
|
||||
expect(def.children!.length).toBe(3);
|
||||
});
|
||||
|
||||
it('Test union export', () => {
|
||||
const node = nodes.find((n) => getNodeName(n) === 'aUnionProperty');
|
||||
expect(node).toBeDefined();
|
||||
const def = buildApiDeclaration(node!, plugins, log, plugins[0].manifest.id, ApiScope.CLIENT);
|
||||
expect(def.type).toBe(TypeKind.CompoundTypeKind);
|
||||
});
|
||||
|
||||
it('Function inside interface has a label', () => {
|
||||
const node = nodes.find((n) => getNodeName(n) === 'ExampleInterface');
|
||||
expect(node).toBeDefined();
|
||||
const def = buildApiDeclaration(node!, plugins, log, plugins[0].manifest.id, ApiScope.CLIENT);
|
||||
|
||||
const fn = def!.children?.find((c) => c.label === 'aFn');
|
||||
expect(fn).toBeDefined();
|
||||
expect(fn?.label).toBe('aFn');
|
||||
expect(fn?.type).toBe(TypeKind.FunctionKind);
|
||||
});
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Node } from 'ts-morph';
|
||||
import { ToolingLog, KibanaPlatformPlugin } from '@kbn/dev-utils';
|
||||
import { buildClassDec } from './build_class_dec';
|
||||
import { buildFunctionDec } from './build_function_dec';
|
||||
import { getCommentsFromNode } from './js_doc_utils';
|
||||
import { isNamedNode } from '../tsmorph_utils';
|
||||
import { AnchorLink, ApiDeclaration } from '../types';
|
||||
import { buildVariableDec } from './build_variable_dec';
|
||||
import { getApiSectionId } from '../utils';
|
||||
import { getSourceForNode } from './utils';
|
||||
import { buildTypeLiteralDec } from './build_type_literal_dec';
|
||||
import { ApiScope } from '../types';
|
||||
import { getSignature } from './get_signature';
|
||||
import { buildInterfaceDec } from './build_interface_dec';
|
||||
import { getTypeKind } from './get_type_kind';
|
||||
|
||||
/**
|
||||
* A potentially recursive function, depending on the node type, that builds a JSON like structure
|
||||
* that can be passed to the elastic-docs component for rendering as an API. Nodes like classes,
|
||||
* interfaces, objects and functions will have children for their properties, members and parameters.
|
||||
*
|
||||
* @param node The ts-morph node to build an ApiDeclaration for.
|
||||
* @param plugins The list of plugins registered is used for building cross plugin links by looking up
|
||||
* the plugin by import path. We could accomplish the same thing via a regex on the import path, but this lets us
|
||||
* decouple plugin path from plugin id.
|
||||
* @param log Logs messages to console.
|
||||
* @param pluginName The name of the plugin this declaration belongs to.
|
||||
* @param scope The scope this declaration belongs to (server, public, or common).
|
||||
* @param parentApiId If this declaration is nested inside another declaration, it should have a parent id. This
|
||||
* is used to create the anchor link to this API item.
|
||||
* @param name An optional name to pass through which will be used instead of node.getName, if it
|
||||
* exists. For some types, like Parameters, the name comes on the parent node, but we want the doc def
|
||||
* to be built from the TypedNode
|
||||
*/
|
||||
export function buildApiDeclaration(
|
||||
node: Node,
|
||||
plugins: KibanaPlatformPlugin[],
|
||||
log: ToolingLog,
|
||||
pluginName: string,
|
||||
scope: ApiScope,
|
||||
parentApiId?: string,
|
||||
name?: string
|
||||
): ApiDeclaration {
|
||||
const apiName = name ? name : isNamedNode(node) ? node.getName() : 'Unnamed';
|
||||
log.debug(`Building API Declaration for ${apiName} of kind ${node.getKindName()}`);
|
||||
const apiId = parentApiId ? parentApiId + '.' + apiName : apiName;
|
||||
const anchorLink: AnchorLink = { scope, pluginName, apiName: apiId };
|
||||
|
||||
if (Node.isClassDeclaration(node)) {
|
||||
return buildClassDec(node, plugins, anchorLink, log);
|
||||
} else if (Node.isInterfaceDeclaration(node)) {
|
||||
return buildInterfaceDec(node, plugins, anchorLink, log);
|
||||
} else if (
|
||||
Node.isMethodSignature(node) ||
|
||||
Node.isFunctionDeclaration(node) ||
|
||||
Node.isMethodDeclaration(node) ||
|
||||
Node.isConstructorDeclaration(node)
|
||||
) {
|
||||
return buildFunctionDec(node, plugins, anchorLink, log);
|
||||
} else if (
|
||||
Node.isPropertySignature(node) ||
|
||||
Node.isPropertyDeclaration(node) ||
|
||||
Node.isShorthandPropertyAssignment(node) ||
|
||||
Node.isPropertyAssignment(node) ||
|
||||
Node.isVariableDeclaration(node)
|
||||
) {
|
||||
return buildVariableDec(node, plugins, anchorLink, log);
|
||||
} else if (Node.isTypeLiteralNode(node)) {
|
||||
return buildTypeLiteralDec(node, plugins, anchorLink, log, apiName);
|
||||
}
|
||||
|
||||
return {
|
||||
id: getApiSectionId(anchorLink),
|
||||
type: getTypeKind(node),
|
||||
label: apiName,
|
||||
description: getCommentsFromNode(node),
|
||||
source: getSourceForNode(node),
|
||||
signature: getSignature(node, plugins, log),
|
||||
};
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ToolingLog, KibanaPlatformPlugin } from '@kbn/dev-utils';
|
||||
|
||||
import {
|
||||
ArrowFunction,
|
||||
VariableDeclaration,
|
||||
PropertyDeclaration,
|
||||
PropertySignature,
|
||||
ShorthandPropertyAssignment,
|
||||
PropertyAssignment,
|
||||
} from 'ts-morph';
|
||||
import { getApiSectionId } from '../utils';
|
||||
import { getCommentsFromNode } from './js_doc_utils';
|
||||
import { AnchorLink, TypeKind } from '../types';
|
||||
import { getSourceForNode } from './utils';
|
||||
import { buildApiDecsForParameters } from './build_parameter_decs';
|
||||
import { getSignature } from './get_signature';
|
||||
import { getJSDocReturnTagComment } from './js_doc_utils';
|
||||
|
||||
/**
|
||||
* Arrow functions are handled differently than regular functions because you need the arrow function
|
||||
* initializer as well as the node. The initializer is where the parameters are grabbed from and the
|
||||
* signature, while the node has the comments and name.
|
||||
*
|
||||
* @param node
|
||||
* @param initializer
|
||||
* @param plugins
|
||||
* @param anchorLink
|
||||
* @param log
|
||||
*/
|
||||
export function getArrowFunctionDec(
|
||||
node:
|
||||
| VariableDeclaration
|
||||
| PropertyDeclaration
|
||||
| PropertySignature
|
||||
| ShorthandPropertyAssignment
|
||||
| PropertyAssignment,
|
||||
initializer: ArrowFunction,
|
||||
plugins: KibanaPlatformPlugin[],
|
||||
anchorLink: AnchorLink,
|
||||
log: ToolingLog
|
||||
) {
|
||||
log.debug(
|
||||
`Getting Arrow Function doc def for node ${node.getName()} of kind ${node.getKindName()}`
|
||||
);
|
||||
return {
|
||||
id: getApiSectionId(anchorLink),
|
||||
type: TypeKind.FunctionKind,
|
||||
children: buildApiDecsForParameters(initializer.getParameters(), plugins, anchorLink, log),
|
||||
signature: getSignature(initializer, plugins, log),
|
||||
description: getCommentsFromNode(node),
|
||||
label: node.getName(),
|
||||
source: getSourceForNode(node),
|
||||
returnComment: getJSDocReturnTagComment(node),
|
||||
};
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ToolingLog, KibanaPlatformPlugin } from '@kbn/dev-utils';
|
||||
import { ClassDeclaration } from 'ts-morph';
|
||||
import { AnchorLink, ApiDeclaration, TypeKind } from '../types';
|
||||
import { getCommentsFromNode } from './js_doc_utils';
|
||||
import { buildApiDeclaration } from './build_api_declaration';
|
||||
import { getSourceForNode, isPrivate } from './utils';
|
||||
import { getApiSectionId } from '../utils';
|
||||
import { getSignature } from './get_signature';
|
||||
|
||||
export function buildClassDec(
|
||||
node: ClassDeclaration,
|
||||
plugins: KibanaPlatformPlugin[],
|
||||
anchorLink: AnchorLink,
|
||||
log: ToolingLog
|
||||
): ApiDeclaration {
|
||||
return {
|
||||
id: getApiSectionId(anchorLink),
|
||||
type: TypeKind.ClassKind,
|
||||
label: node.getName() || 'Missing label',
|
||||
description: getCommentsFromNode(node),
|
||||
signature: getSignature(node, plugins, log),
|
||||
children: node.getMembers().reduce((acc, m) => {
|
||||
if (!isPrivate(m)) {
|
||||
acc.push(
|
||||
buildApiDeclaration(
|
||||
m,
|
||||
plugins,
|
||||
log,
|
||||
anchorLink.pluginName,
|
||||
anchorLink.scope,
|
||||
anchorLink.apiName
|
||||
)
|
||||
);
|
||||
}
|
||||
return acc;
|
||||
}, [] as ApiDeclaration[]),
|
||||
source: getSourceForNode(node),
|
||||
};
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
||||
FunctionDeclaration,
|
||||
MethodDeclaration,
|
||||
ConstructorDeclaration,
|
||||
Node,
|
||||
MethodSignature,
|
||||
} from 'ts-morph';
|
||||
|
||||
import { ToolingLog, KibanaPlatformPlugin } from '@kbn/dev-utils';
|
||||
import { buildApiDecsForParameters } from './build_parameter_decs';
|
||||
import { AnchorLink, ApiDeclaration, TypeKind } from '../types';
|
||||
import { getCommentsFromNode } from './js_doc_utils';
|
||||
import { getApiSectionId } from '../utils';
|
||||
import { getJSDocReturnTagComment, getJSDocs, getJSDocTagNames } from './js_doc_utils';
|
||||
import { getSourceForNode } from './utils';
|
||||
import { getSignature } from './get_signature';
|
||||
|
||||
/**
|
||||
* Takes the various function-like node declaration types and converts them into an ApiDeclaration.
|
||||
* @param node
|
||||
* @param plugins
|
||||
* @param anchorLink
|
||||
* @param log
|
||||
*/
|
||||
export function buildFunctionDec(
|
||||
node: FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature,
|
||||
plugins: KibanaPlatformPlugin[],
|
||||
anchorLink: AnchorLink,
|
||||
log: ToolingLog
|
||||
): ApiDeclaration {
|
||||
const label = Node.isConstructorDeclaration(node)
|
||||
? 'Constructor'
|
||||
: node.getName() || '(WARN: Missing name)';
|
||||
log.debug(`Getting function doc def for node ${label} of kind ${node.getKindName()}`);
|
||||
return {
|
||||
id: getApiSectionId(anchorLink),
|
||||
type: TypeKind.FunctionKind,
|
||||
label,
|
||||
signature: getSignature(node, plugins, log),
|
||||
description: getCommentsFromNode(node),
|
||||
children: buildApiDecsForParameters(
|
||||
node.getParameters(),
|
||||
plugins,
|
||||
anchorLink,
|
||||
log,
|
||||
getJSDocs(node)
|
||||
),
|
||||
tags: getJSDocTagNames(node),
|
||||
returnComment: getJSDocReturnTagComment(node),
|
||||
source: getSourceForNode(node),
|
||||
};
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ToolingLog, KibanaPlatformPlugin } from '@kbn/dev-utils';
|
||||
import { FunctionTypeNode, JSDoc } from 'ts-morph';
|
||||
import { getApiSectionId } from '../utils';
|
||||
import { getCommentsFromNode } from './js_doc_utils';
|
||||
import { AnchorLink, ApiDeclaration, TypeKind } from '../types';
|
||||
import { buildApiDecsForParameters } from './build_parameter_decs';
|
||||
import { extractImportReferences } from './extract_import_refs';
|
||||
import { getJSDocReturnTagComment, getJSDocs, getJSDocTagNames } from './js_doc_utils';
|
||||
import { getSourceForNode } from './utils';
|
||||
|
||||
export function buildApiDecFromFunctionType(
|
||||
name: string,
|
||||
node: FunctionTypeNode,
|
||||
plugins: KibanaPlatformPlugin[],
|
||||
anchorLink: AnchorLink,
|
||||
log: ToolingLog,
|
||||
jsDocs?: JSDoc[]
|
||||
): ApiDeclaration {
|
||||
log.debug(`Getting Function Type doc def for node ${name} of kind ${node.getKindName()}`);
|
||||
return {
|
||||
type: TypeKind.FunctionKind,
|
||||
id: getApiSectionId(anchorLink),
|
||||
label: name,
|
||||
signature: extractImportReferences(node.getType().getText(), plugins, log),
|
||||
description: getCommentsFromNode(node),
|
||||
tags: jsDocs ? getJSDocTagNames(jsDocs) : [],
|
||||
returnComment: jsDocs ? getJSDocReturnTagComment(jsDocs) : [],
|
||||
children: buildApiDecsForParameters(
|
||||
node.getParameters(),
|
||||
plugins,
|
||||
anchorLink,
|
||||
log,
|
||||
jsDocs || getJSDocs(node)
|
||||
),
|
||||
source: getSourceForNode(node),
|
||||
};
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ToolingLog, KibanaPlatformPlugin } from '@kbn/dev-utils';
|
||||
import { InterfaceDeclaration } from 'ts-morph';
|
||||
import { AnchorLink, ApiDeclaration, TypeKind } from '../types';
|
||||
import { getCommentsFromNode } from './js_doc_utils';
|
||||
import { buildApiDeclaration } from './build_api_declaration';
|
||||
import { getSourceForNode } from './utils';
|
||||
import { getApiSectionId } from '../utils';
|
||||
import { getSignature } from './get_signature';
|
||||
|
||||
export function buildInterfaceDec(
|
||||
node: InterfaceDeclaration,
|
||||
plugins: KibanaPlatformPlugin[],
|
||||
anchorLink: AnchorLink,
|
||||
log: ToolingLog
|
||||
): ApiDeclaration {
|
||||
return {
|
||||
id: getApiSectionId(anchorLink),
|
||||
type: TypeKind.InterfaceKind,
|
||||
label: node.getName(),
|
||||
signature: getSignature(node, plugins, log),
|
||||
description: getCommentsFromNode(node),
|
||||
children: node
|
||||
.getMembers()
|
||||
.map((m) =>
|
||||
buildApiDeclaration(
|
||||
m,
|
||||
plugins,
|
||||
log,
|
||||
anchorLink.pluginName,
|
||||
anchorLink.scope,
|
||||
anchorLink.apiName
|
||||
)
|
||||
),
|
||||
source: getSourceForNode(node),
|
||||
};
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ParameterDeclaration, JSDoc, SyntaxKind } from 'ts-morph';
|
||||
import { ToolingLog, KibanaPlatformPlugin } from '@kbn/dev-utils';
|
||||
import { extractImportReferences } from './extract_import_refs';
|
||||
import { AnchorLink, ApiDeclaration } from '../types';
|
||||
import { buildApiDeclaration } from './build_api_declaration';
|
||||
import { getJSDocParamComment } from './js_doc_utils';
|
||||
import { getSourceForNode } from './utils';
|
||||
import { getTypeKind } from './get_type_kind';
|
||||
|
||||
/**
|
||||
* A helper function to capture function parameters, whether it comes from an arrow function, a regular function or
|
||||
* a function type.
|
||||
*
|
||||
* @param params
|
||||
* @param plugins
|
||||
* @param anchorLink
|
||||
* @param log
|
||||
* @param jsDocs
|
||||
*/
|
||||
export function buildApiDecsForParameters(
|
||||
params: ParameterDeclaration[],
|
||||
plugins: KibanaPlatformPlugin[],
|
||||
anchorLink: AnchorLink,
|
||||
log: ToolingLog,
|
||||
jsDocs?: JSDoc[]
|
||||
): ApiDeclaration[] {
|
||||
return params.reduce((acc, param) => {
|
||||
const label = param.getName();
|
||||
log.debug(`Getting parameter doc def for ${label} of kind ${param.getKindName()}`);
|
||||
// Literal types are non primitives that aren't references to other types. We add them as a more
|
||||
// defined node, with children.
|
||||
// If we don't want the docs to be too deeply nested we could avoid this special handling.
|
||||
if (param.getTypeNode() && param.getTypeNode()!.getKind() === SyntaxKind.TypeLiteral) {
|
||||
acc.push(
|
||||
buildApiDeclaration(
|
||||
param.getTypeNode()!,
|
||||
plugins,
|
||||
log,
|
||||
anchorLink.pluginName,
|
||||
anchorLink.scope,
|
||||
anchorLink.apiName,
|
||||
label
|
||||
)
|
||||
);
|
||||
} else {
|
||||
acc.push({
|
||||
type: getTypeKind(param),
|
||||
label,
|
||||
isRequired: param.getType().isNullable() === false,
|
||||
signature: extractImportReferences(param.getType().getText(), plugins, log),
|
||||
description: jsDocs ? getJSDocParamComment(jsDocs, label) : [],
|
||||
source: getSourceForNode(param),
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, [] as ApiDeclaration[]);
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ToolingLog, KibanaPlatformPlugin } from '@kbn/dev-utils';
|
||||
import { TypeLiteralNode } from 'ts-morph';
|
||||
import { getApiSectionId } from '../utils';
|
||||
import { getCommentsFromNode } from './js_doc_utils';
|
||||
import { AnchorLink, ApiDeclaration, TypeKind } from '../types';
|
||||
import { buildApiDeclaration } from './build_api_declaration';
|
||||
import { getSourceForNode } from './utils';
|
||||
|
||||
/**
|
||||
* This captures function parameters that are object types, and makes sure their
|
||||
* properties are recursively walked so they are expandable in the docs.
|
||||
*
|
||||
* The test verifying `crazyFunction` will fail without this special handling.
|
||||
*
|
||||
* @param node
|
||||
* @param plugins
|
||||
* @param anchorLink
|
||||
* @param log
|
||||
* @param name
|
||||
*/
|
||||
export function buildTypeLiteralDec(
|
||||
node: TypeLiteralNode,
|
||||
plugins: KibanaPlatformPlugin[],
|
||||
anchorLink: AnchorLink,
|
||||
log: ToolingLog,
|
||||
name: string
|
||||
): ApiDeclaration {
|
||||
return {
|
||||
id: getApiSectionId(anchorLink),
|
||||
type: TypeKind.ObjectKind,
|
||||
label: name,
|
||||
description: getCommentsFromNode(node),
|
||||
children: node
|
||||
.getMembers()
|
||||
.map((m) =>
|
||||
buildApiDeclaration(
|
||||
m,
|
||||
plugins,
|
||||
log,
|
||||
anchorLink.pluginName,
|
||||
anchorLink.scope,
|
||||
anchorLink.apiName
|
||||
)
|
||||
),
|
||||
source: getSourceForNode(node),
|
||||
};
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ToolingLog, KibanaPlatformPlugin } from '@kbn/dev-utils';
|
||||
import {
|
||||
VariableDeclaration,
|
||||
Node,
|
||||
PropertyAssignment,
|
||||
PropertyDeclaration,
|
||||
PropertySignature,
|
||||
ShorthandPropertyAssignment,
|
||||
} from 'ts-morph';
|
||||
import { getApiSectionId } from '../utils';
|
||||
import { getCommentsFromNode } from './js_doc_utils';
|
||||
import { AnchorLink, ApiDeclaration, TypeKind } from '../types';
|
||||
import { getArrowFunctionDec } from './build_arrow_fn_dec';
|
||||
import { buildApiDeclaration } from './build_api_declaration';
|
||||
import { getSourceForNode } from './utils';
|
||||
import { getSignature } from './get_signature';
|
||||
import { getTypeKind } from './get_type_kind';
|
||||
|
||||
/**
|
||||
* Special handling for objects and arrow functions which are variable or property node types.
|
||||
* Objects and arrow functions need their children extracted recursively. This uses the name from the
|
||||
* node, but checks for an initializer to get inline arrow functions and objects defined recursively.
|
||||
*
|
||||
* @param node
|
||||
* @param plugins
|
||||
* @param anchorLink
|
||||
* @param log
|
||||
*/
|
||||
export function buildVariableDec(
|
||||
node:
|
||||
| VariableDeclaration
|
||||
| PropertyAssignment
|
||||
| PropertyDeclaration
|
||||
| PropertySignature
|
||||
| ShorthandPropertyAssignment,
|
||||
plugins: KibanaPlatformPlugin[],
|
||||
anchorLink: AnchorLink,
|
||||
log: ToolingLog
|
||||
): ApiDeclaration {
|
||||
log.debug('buildVariableDec for ' + node.getName());
|
||||
const initializer = node.getInitializer();
|
||||
// Recusively list object properties as children.
|
||||
if (initializer && Node.isObjectLiteralExpression(initializer)) {
|
||||
return {
|
||||
id: getApiSectionId(anchorLink),
|
||||
type: TypeKind.ObjectKind,
|
||||
children: initializer.getProperties().map((prop) => {
|
||||
return buildApiDeclaration(
|
||||
prop,
|
||||
plugins,
|
||||
log,
|
||||
anchorLink.pluginName,
|
||||
anchorLink.scope,
|
||||
anchorLink.apiName
|
||||
);
|
||||
}),
|
||||
description: getCommentsFromNode(node),
|
||||
label: node.getName(),
|
||||
source: getSourceForNode(node),
|
||||
};
|
||||
} else if (initializer && Node.isArrowFunction(initializer)) {
|
||||
return getArrowFunctionDec(node, initializer, plugins, anchorLink, log);
|
||||
}
|
||||
|
||||
// Otherwise return it just as a single entry.
|
||||
return {
|
||||
id: getApiSectionId(anchorLink),
|
||||
type: getTypeKind(node),
|
||||
label: node.getName(),
|
||||
description: getCommentsFromNode(node),
|
||||
source: getSourceForNode(node),
|
||||
signature: getSignature(node, plugins, log),
|
||||
};
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { KibanaPlatformPlugin, ToolingLog } from '@kbn/dev-utils';
|
||||
import { getPluginApiDocId } from '../utils';
|
||||
import { extractImportReferences } from './extract_import_refs';
|
||||
import { ApiScope, Reference } from '../types';
|
||||
import { getKibanaPlatformPlugin } from '../tests/kibana_platform_plugin_mock';
|
||||
|
||||
const plugin = getKibanaPlatformPlugin('pluginA');
|
||||
const plugins: KibanaPlatformPlugin[] = [plugin];
|
||||
|
||||
const log = new ToolingLog({
|
||||
level: 'debug',
|
||||
writeTo: process.stdout,
|
||||
});
|
||||
|
||||
it('when there are no imports', () => {
|
||||
const results = extractImportReferences(`(param: string) => Bar`, plugins, log);
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0]).toBe('(param: string) => Bar');
|
||||
});
|
||||
|
||||
it('test extractImportReference', () => {
|
||||
const results = extractImportReferences(
|
||||
`(param: string) => import("${plugin.directory}/public/bar").Bar`,
|
||||
plugins,
|
||||
log
|
||||
);
|
||||
expect(results.length).toBe(2);
|
||||
expect(results[0]).toBe('(param: string) => ');
|
||||
expect(results[1]).toEqual({
|
||||
text: 'Bar',
|
||||
docId: getPluginApiDocId('plugin_a', log),
|
||||
section: 'def-public.Bar',
|
||||
pluginId: 'pluginA',
|
||||
scope: ApiScope.CLIENT,
|
||||
});
|
||||
});
|
||||
|
||||
it('test extractImportReference with public folder nested under server folder', () => {
|
||||
const results = extractImportReferences(
|
||||
`import("${plugin.directory}/server/routes/public/bar").Bar`,
|
||||
plugins,
|
||||
log
|
||||
);
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0]).toEqual({
|
||||
text: 'Bar',
|
||||
docId: getPluginApiDocId('plugin_a', log),
|
||||
section: 'def-server.Bar',
|
||||
pluginId: 'pluginA',
|
||||
scope: ApiScope.SERVER,
|
||||
});
|
||||
});
|
||||
|
||||
it('test extractImportReference with two imports', () => {
|
||||
const results = extractImportReferences(
|
||||
`<I extends import("${plugin.directory}/public/foo/index").FooFoo, O extends import("${plugin.directory}/public/bar").Bar>`,
|
||||
plugins,
|
||||
log
|
||||
);
|
||||
expect(results.length).toBe(5);
|
||||
expect(results[0]).toBe('<I extends ');
|
||||
expect((results[1] as Reference).text).toBe('FooFoo');
|
||||
expect(results[2]).toBe(', O extends ');
|
||||
expect((results[3] as Reference).text).toBe('Bar');
|
||||
expect(results[4]).toBe('>');
|
||||
});
|
||||
|
||||
it('test extractImportReference with unknown imports', () => {
|
||||
const results = extractImportReferences(
|
||||
`<I extends import("/plugin_c/public/foo/index").FooFoo>`,
|
||||
plugins,
|
||||
log
|
||||
);
|
||||
expect(results.length).toBe(3);
|
||||
expect(results[0]).toBe('<I extends ');
|
||||
expect(results[1]).toBe('FooFoo');
|
||||
expect(results[2]).toBe('>');
|
||||
});
|
||||
|
||||
it('test single link', () => {
|
||||
const results = extractImportReferences(
|
||||
`import("${plugin.directory}/public/foo/index").FooFoo`,
|
||||
plugins,
|
||||
log
|
||||
);
|
||||
expect(results.length).toBe(1);
|
||||
expect((results[0] as Reference).text).toBe('FooFoo');
|
||||
});
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { KibanaPlatformPlugin, ToolingLog } from '@kbn/dev-utils';
|
||||
import { getApiSectionId, getPluginApiDocId, getPluginForPath } from '../utils';
|
||||
import { ApiScope, TextWithLinks } from '../types';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param text A string that may include an API item that was imported from another file. For example:
|
||||
* "export type foo = string | import("kibana/src/plugins/a_plugin/public/path").Bar".
|
||||
* @param plugins The list of registered Kibana plugins. Used to get the plugin id, which is then used to create
|
||||
* the DocLink to that plugin's page, based off the relative path of any imports.
|
||||
* @param log Logging utility for debuging
|
||||
*
|
||||
* @returns An array structure that can be used to create react DocLinks. For example, the above text would return
|
||||
* something like:
|
||||
* [ "export type foo = string | ", // Just a string for the pretext
|
||||
* { id: "a_plugin", section: "public.Bar", text: "Bar" } // An object with info to create the DocLink.
|
||||
* ]
|
||||
*/
|
||||
export function extractImportReferences(
|
||||
text: string,
|
||||
plugins: KibanaPlatformPlugin[],
|
||||
log: ToolingLog
|
||||
): TextWithLinks {
|
||||
const texts: TextWithLinks = [];
|
||||
let pos = 0;
|
||||
let textSegment: string | undefined = text;
|
||||
const max = 5;
|
||||
while (textSegment) {
|
||||
pos++;
|
||||
if (pos > max) break;
|
||||
|
||||
const ref = extractImportRef(textSegment);
|
||||
if (ref) {
|
||||
const { name, path, index, length } = ref;
|
||||
if (index !== 0) {
|
||||
texts.push(textSegment.substr(0, index));
|
||||
}
|
||||
const plugin = getPluginForPath(path, plugins);
|
||||
|
||||
if (!plugin) {
|
||||
if (path.indexOf('plugin') >= 0) {
|
||||
log.warning('WARN: no plugin found for reference path ' + path);
|
||||
}
|
||||
// If we can't create a link for this, still remove the import("..."). part to make
|
||||
// it easier to read.
|
||||
const str = textSegment.substr(index + length - name.length, name.length);
|
||||
if (str && str !== '') {
|
||||
texts.push(str);
|
||||
}
|
||||
} else {
|
||||
const section = getApiSectionId({
|
||||
pluginName: plugin.manifest.id,
|
||||
scope: getScopeFromPath(path, plugin, log),
|
||||
apiName: name,
|
||||
});
|
||||
texts.push({
|
||||
pluginId: plugin.manifest.id,
|
||||
scope: getScopeFromPath(path, plugin, log),
|
||||
docId: getPluginApiDocId(plugin.manifest.id, log, {
|
||||
serviceFolders: plugin.manifest.serviceFolders,
|
||||
apiPath: path,
|
||||
directory: plugin.directory,
|
||||
}),
|
||||
section,
|
||||
text: name,
|
||||
});
|
||||
}
|
||||
textSegment = textSegment.substr(index + length);
|
||||
} else {
|
||||
if (textSegment && textSegment !== '') {
|
||||
texts.push(textSegment);
|
||||
}
|
||||
textSegment = undefined;
|
||||
}
|
||||
}
|
||||
return texts;
|
||||
}
|
||||
|
||||
function extractImportRef(
|
||||
str: string
|
||||
): { path: string; name: string; index: number; length: number } | undefined {
|
||||
const groups = str.match(/import\("(.*?)"\)\.(\w*)/);
|
||||
if (groups) {
|
||||
const path = groups[1];
|
||||
const name = groups[2];
|
||||
const index = groups.index!;
|
||||
const length = groups[0].length;
|
||||
return { path, name, index, length };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param path An absolute path to a file inside a plugin directory.
|
||||
*/
|
||||
function getScopeFromPath(path: string, plugin: KibanaPlatformPlugin, log: ToolingLog): ApiScope {
|
||||
if (path.startsWith(`${plugin.directory}/public/`)) {
|
||||
return ApiScope.CLIENT;
|
||||
} else if (path.startsWith(`${plugin.directory}/server/`)) {
|
||||
return ApiScope.SERVER;
|
||||
} else if (path.startsWith(`${plugin.directory}/common/`)) {
|
||||
return ApiScope.COMMON;
|
||||
} else {
|
||||
log.warning(`Unexpected path encountered ${path}`);
|
||||
return ApiScope.COMMON;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { KibanaPlatformPlugin, ToolingLog } from '@kbn/dev-utils';
|
||||
import { Node, Type } from 'ts-morph';
|
||||
import { isNamedNode } from '../tsmorph_utils';
|
||||
import { Reference } from '../types';
|
||||
import { extractImportReferences } from './extract_import_refs';
|
||||
import { getTypeKind } from './get_type_kind';
|
||||
|
||||
/**
|
||||
* Special logic for creating the signature based on the type of node. See https://github.com/dsherret/ts-morph/issues/923#issue-795332729
|
||||
* for some issues that have been encountered in getting these accurate.
|
||||
*
|
||||
* By passing node to `getText`, ala `node.getType().getText(node)`, all reference links
|
||||
* will be lost. However, if you do _not_ pass node, there are quite a few situations where it returns a reference
|
||||
* to itself and has no helpful information.
|
||||
*
|
||||
* @param node
|
||||
* @param plugins
|
||||
* @param log
|
||||
*/
|
||||
export function getSignature(
|
||||
node: Node,
|
||||
plugins: KibanaPlatformPlugin[],
|
||||
log: ToolingLog
|
||||
): Array<string | Reference> | undefined {
|
||||
let signature = '';
|
||||
// node.getType() on a TypeAliasDeclaration is just a reference to itself. If we don't special case this, then
|
||||
// `export type Foo = string | number;` would show up with a signagure of `Foo` that is a link to itself, instead of
|
||||
// `string | number`.
|
||||
if (Node.isTypeAliasDeclaration(node)) {
|
||||
signature = getSignatureForTypeAlias(node.getType(), log, node);
|
||||
} else if (Node.isFunctionDeclaration(node)) {
|
||||
// See https://github.com/dsherret/ts-morph/issues/907#issue-770284331.
|
||||
// Unfortunately this has to be manually pieced together, or it comes up as "typeof TheFunction"
|
||||
const params = node
|
||||
.getParameters()
|
||||
.map((p) => `${p.getName()}: ${p.getType().getText()}`)
|
||||
.join(', ');
|
||||
const returnType = node.getReturnType().getText();
|
||||
signature = `(${params}) => ${returnType}`;
|
||||
} else if (Node.isInterfaceDeclaration(node) || Node.isClassDeclaration(node)) {
|
||||
// Need to tack on manually any type parameters or "extends/implements" section.
|
||||
const heritageClause = node
|
||||
.getHeritageClauses()
|
||||
.map((h) => {
|
||||
const heritance = h.getText().indexOf('implements') > -1 ? 'implements' : 'extends';
|
||||
return `${heritance} ${h.getTypeNodes().map((n) => n.getType().getText())}`;
|
||||
})
|
||||
.join(' ');
|
||||
signature = `${node.getType().getText()}${heritageClause ? ' ' + heritageClause : ''}`;
|
||||
} else {
|
||||
// Here, 'node' is explicitly *not* passed in to `getText` otherwise arrow functions won't
|
||||
// include reference links. Tests will break if you add it in here, or remove it from above.
|
||||
// There is test coverage for all this oddness.
|
||||
signature = node.getType().getText();
|
||||
}
|
||||
|
||||
// Don't return the signature if it's the same as the type (string, string)
|
||||
if (getTypeKind(node).toString() === signature) return undefined;
|
||||
|
||||
const referenceLinks = extractImportReferences(signature, plugins, log);
|
||||
|
||||
// Don't return the signature if it's a single self referential link.
|
||||
if (
|
||||
isNamedNode(node) &&
|
||||
referenceLinks.length === 1 &&
|
||||
typeof referenceLinks[0] === 'object' &&
|
||||
(referenceLinks[0] as Reference).text === node.getName()
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return referenceLinks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not all types are handled here, but does return links for the more common ones.
|
||||
*/
|
||||
function getSignatureForTypeAlias(type: Type, log: ToolingLog, node?: Node): string {
|
||||
if (type.isUnion()) {
|
||||
return type
|
||||
.getUnionTypes()
|
||||
.map((nestedType) => getSignatureForTypeAlias(nestedType, log))
|
||||
.join(' | ');
|
||||
} else if (node && type.getCallSignatures().length >= 1) {
|
||||
return type
|
||||
.getCallSignatures()
|
||||
.map((sig) => {
|
||||
const params = sig
|
||||
.getParameters()
|
||||
.map((p) => `${p.getName()}: ${p.getTypeAtLocation(node).getText()}`)
|
||||
.join(', ');
|
||||
const returnType = sig.getReturnType().getText();
|
||||
return `(${params}) => ${returnType}`;
|
||||
})
|
||||
.join(' ');
|
||||
} else if (node) {
|
||||
const symbol = node.getSymbol();
|
||||
if (symbol) {
|
||||
const declarations = symbol
|
||||
.getDeclarations()
|
||||
.map((d) => d.getType().getText(node))
|
||||
.join(' ');
|
||||
if (symbol.getDeclarations().length !== 1) {
|
||||
log.error(
|
||||
`Node is type alias declaration with more than one declaration. This is not handled! ${declarations} and node is ${node.getText()}`
|
||||
);
|
||||
}
|
||||
return declarations;
|
||||
}
|
||||
}
|
||||
return type.getText();
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Type, Node } from 'ts-morph';
|
||||
import { TypeKind } from '../types';
|
||||
|
||||
export function getTypeKind(node: Node): TypeKind {
|
||||
if (Node.isTypeAliasDeclaration(node)) {
|
||||
return TypeKind.TypeKind;
|
||||
} else {
|
||||
return getTypeKindForType(node.getType());
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeKindForType(type: Type): TypeKind {
|
||||
// I think a string literal is also a string... but just in case, checking both.
|
||||
if (type.isString() || type.isStringLiteral()) {
|
||||
return TypeKind.StringKind;
|
||||
} else if (type.isNumber() || type.isNumberLiteral()) {
|
||||
return TypeKind.NumberKind;
|
||||
|
||||
// I could be wrong about this logic. Does this existance of a call signature mean it's a function?
|
||||
} else if (type.getCallSignatures().length > 0) {
|
||||
return TypeKind.FunctionKind;
|
||||
} else if (type.isArray()) {
|
||||
// Arrays are also objects, check this first.
|
||||
return TypeKind.ArrayKind;
|
||||
} else if (type.isObject()) {
|
||||
return TypeKind.ObjectKind;
|
||||
} else if (type.isBoolean() || type.isBooleanLiteral()) {
|
||||
return TypeKind.BooleanKind;
|
||||
} else if (type.isEnum() || type.isEnumLiteral()) {
|
||||
return TypeKind.EnumKind;
|
||||
} else if (type.isUnion()) {
|
||||
// Special handling for "type | undefined" which happens alot and should be represented in docs as
|
||||
// "type", but with an "optional" flag. Anything more complicated will just be returned as a
|
||||
// "CompoundType".
|
||||
if (getIsTypeOptional(type) && type.getUnionTypes().length === 2) {
|
||||
const otherType = type.getUnionTypes().find((u) => u.isUndefined() === false);
|
||||
if (otherType) {
|
||||
return getTypeKindForType(otherType);
|
||||
}
|
||||
}
|
||||
} else if (type.isAny()) {
|
||||
return TypeKind.AnyKind;
|
||||
} else if (type.isUnknown()) {
|
||||
return TypeKind.UnknownKind;
|
||||
}
|
||||
|
||||
if (type.isUnionOrIntersection()) {
|
||||
return TypeKind.CompoundTypeKind;
|
||||
}
|
||||
|
||||
return TypeKind.Uncategorized;
|
||||
}
|
||||
|
||||
function getIsTypeOptional(type: Type): boolean {
|
||||
if (type.isUnion()) {
|
||||
const unions = type.getUnionTypes();
|
||||
return unions.find((u) => u.isUndefined()) !== undefined;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { JSDoc, JSDocTag, Node } from 'ts-morph';
|
||||
import { TextWithLinks } from '../types';
|
||||
|
||||
/**
|
||||
* Extracts comments out of the node to use as the description.
|
||||
*/
|
||||
export function getCommentsFromNode(node: Node): TextWithLinks | undefined {
|
||||
let comments: TextWithLinks | undefined;
|
||||
const jsDocs = getJSDocs(node);
|
||||
if (jsDocs) {
|
||||
return getTextWithLinks(jsDocs.map((jsDoc) => jsDoc.getDescription()).join('\n'));
|
||||
} else {
|
||||
comments = getTextWithLinks(
|
||||
node
|
||||
.getLeadingCommentRanges()
|
||||
.map((c) => c.getText())
|
||||
.join('\n')
|
||||
);
|
||||
}
|
||||
|
||||
return comments;
|
||||
}
|
||||
|
||||
export function getJSDocs(node: Node): JSDoc[] | undefined {
|
||||
if (Node.isJSDocableNode(node)) {
|
||||
return node.getJsDocs();
|
||||
} else if (Node.isVariableDeclaration(node)) {
|
||||
const gparent = node.getParent()?.getParent();
|
||||
if (Node.isJSDocableNode(gparent)) {
|
||||
return gparent.getJsDocs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getJSDocReturnTagComment(node: Node | JSDoc[]): TextWithLinks {
|
||||
const tags = getJSDocTags(node);
|
||||
const returnTag = tags.find((tag) => Node.isJSDocReturnTag(tag));
|
||||
if (returnTag) return getTextWithLinks(returnTag.getComment());
|
||||
return [];
|
||||
}
|
||||
|
||||
export function getJSDocParamComment(node: Node | JSDoc[], name: string): TextWithLinks {
|
||||
const tags = getJSDocTags(node);
|
||||
const paramTag = tags.find((tag) => Node.isJSDocParameterTag(tag) && tag.getName() === name);
|
||||
if (paramTag) return getTextWithLinks(paramTag.getComment());
|
||||
return [];
|
||||
}
|
||||
|
||||
export function getJSDocTagNames(node: Node | JSDoc[]): string[] {
|
||||
return getJSDocTags(node).reduce((tags, tag) => {
|
||||
if (tag.getTagName() !== 'param' && tag.getTagName() !== 'returns') {
|
||||
tags.push(tag.getTagName());
|
||||
}
|
||||
return tags;
|
||||
}, [] as string[]);
|
||||
}
|
||||
|
||||
function getJSDocTags(node: Node | JSDoc[]): JSDocTag[] {
|
||||
const jsDocs = node instanceof Array ? node : getJSDocs(node);
|
||||
if (!jsDocs) return [];
|
||||
|
||||
return jsDocs.reduce((tagsAcc, jsDoc) => {
|
||||
tagsAcc.push(...jsDoc.getTags());
|
||||
return tagsAcc;
|
||||
}, [] as JSDocTag[]);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO. This feature is not implemented yet. It will be used to create links for comments
|
||||
* that use {@link AnotherAPIItemInThisPlugin}.
|
||||
*
|
||||
* @param text
|
||||
*/
|
||||
function getTextWithLinks(text?: string): TextWithLinks {
|
||||
if (text) return [text];
|
||||
else return [];
|
||||
// TODO:
|
||||
// Replace `@links` in comments with relative api links.
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import Path from 'path';
|
||||
import { REPO_ROOT, kibanaPackageJson } from '@kbn/utils';
|
||||
import { ParameterDeclaration, ClassMemberTypes, Node } from 'ts-morph';
|
||||
import { SourceLink } from '../types';
|
||||
|
||||
export function isPrivate(node: ParameterDeclaration | ClassMemberTypes): boolean {
|
||||
return node.getModifiers().find((mod) => mod.getText() === 'private') !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the absolute path into a relative one.
|
||||
*/
|
||||
function getRelativePath(fullPath: string): string {
|
||||
return Path.relative(REPO_ROOT, fullPath);
|
||||
}
|
||||
|
||||
export function getSourceForNode(node: Node): SourceLink {
|
||||
const path = getRelativePath(node.getSourceFile().getFilePath());
|
||||
const lineNumber = node.getStartLineNumber();
|
||||
return {
|
||||
path,
|
||||
lineNumber,
|
||||
link: `https://github.com/elastic/kibana/tree/${kibanaPackageJson.branch}${path}#L${lineNumber}`,
|
||||
};
|
||||
}
|
152
packages/kbn-docs-utils/src/api_docs/build_api_docs_cli.ts
Normal file
152
packages/kbn-docs-utils/src/api_docs/build_api_docs_cli.ts
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import Fs from 'fs';
|
||||
import Path from 'path';
|
||||
|
||||
import { REPO_ROOT, run } from '@kbn/dev-utils';
|
||||
import { Project } from 'ts-morph';
|
||||
|
||||
import { getPluginApi } from './get_plugin_api';
|
||||
import { writePluginDocs } from './mdx/write_plugin_mdx_docs';
|
||||
import { ApiDeclaration, PluginApi } from './types';
|
||||
import { findPlugins } from './find_plugins';
|
||||
import { removeBrokenLinks } from './utils';
|
||||
|
||||
export interface PluginInfo {
|
||||
apiCount: number;
|
||||
apiCountMissingComments: number;
|
||||
id: string;
|
||||
missingApiItems: string[];
|
||||
}
|
||||
|
||||
export function runBuildApiDocsCli() {
|
||||
run(
|
||||
async ({ log }) => {
|
||||
const project = getTsProject(REPO_ROOT);
|
||||
|
||||
const plugins = findPlugins();
|
||||
|
||||
const pluginInfos: {
|
||||
[key: string]: PluginInfo;
|
||||
} = {};
|
||||
|
||||
const outputFolder = Path.resolve(REPO_ROOT, 'api_docs');
|
||||
if (!Fs.existsSync(outputFolder)) {
|
||||
Fs.mkdirSync(outputFolder);
|
||||
} else {
|
||||
// Delete all files except the README that warns about the auto-generated nature of
|
||||
// the folder.
|
||||
const files = Fs.readdirSync(outputFolder);
|
||||
files.forEach((file) => {
|
||||
if (file.indexOf('README.md') < 0) {
|
||||
Fs.rmSync(Path.resolve(outputFolder, file));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const pluginApiMap: { [key: string]: PluginApi } = {};
|
||||
plugins.map((plugin) => {
|
||||
pluginApiMap[plugin.manifest.id] = getPluginApi(project, plugin, plugins, log);
|
||||
});
|
||||
|
||||
const missingApiItems: { [key: string]: string[] } = {};
|
||||
|
||||
plugins.forEach((plugin) => {
|
||||
const id = plugin.manifest.id;
|
||||
const pluginApi = pluginApiMap[id];
|
||||
removeBrokenLinks(pluginApi, missingApiItems, pluginApiMap);
|
||||
});
|
||||
|
||||
plugins.forEach((plugin) => {
|
||||
const id = plugin.manifest.id;
|
||||
const pluginApi = pluginApiMap[id];
|
||||
const info = {
|
||||
id,
|
||||
apiCount: countApiForPlugin(pluginApi),
|
||||
apiCountMissingComments: countMissingCommentsApiForPlugin(pluginApi),
|
||||
missingApiItems: missingApiItems[id],
|
||||
};
|
||||
|
||||
if (info.apiCount > 0) {
|
||||
writePluginDocs(outputFolder, pluginApi, log);
|
||||
pluginInfos[id] = info;
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.table(pluginInfos);
|
||||
},
|
||||
{
|
||||
log: {
|
||||
defaultLevel: 'debug',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getTsProject(repoPath: string) {
|
||||
const xpackTsConfig = `${repoPath}/tsconfig.json`;
|
||||
const project = new Project({
|
||||
tsConfigFilePath: xpackTsConfig,
|
||||
});
|
||||
project.addSourceFilesAtPaths(`${repoPath}/x-pack/plugins/**/*{.d.ts,.ts}`);
|
||||
project.resolveSourceFileDependencies();
|
||||
return project;
|
||||
}
|
||||
|
||||
function countMissingCommentsApiForPlugin(doc: PluginApi) {
|
||||
return (
|
||||
doc.client.reduce((sum, def) => {
|
||||
return sum + countMissingCommentsForApi(def);
|
||||
}, 0) +
|
||||
doc.server.reduce((sum, def) => {
|
||||
return sum + countMissingCommentsForApi(def);
|
||||
}, 0) +
|
||||
doc.common.reduce((sum, def) => {
|
||||
return sum + countMissingCommentsForApi(def);
|
||||
}, 0)
|
||||
);
|
||||
}
|
||||
|
||||
function countMissingCommentsForApi(doc: ApiDeclaration): number {
|
||||
const missingCnt = doc.description && doc.description.length > 0 ? 0 : 1;
|
||||
if (!doc.children) return missingCnt;
|
||||
else
|
||||
return (
|
||||
missingCnt +
|
||||
doc.children.reduce((sum, child) => {
|
||||
return sum + countMissingCommentsForApi(child);
|
||||
}, 0)
|
||||
);
|
||||
}
|
||||
|
||||
function countApiForPlugin(doc: PluginApi) {
|
||||
return (
|
||||
doc.client.reduce((sum, def) => {
|
||||
return sum + countApi(def);
|
||||
}, 0) +
|
||||
doc.server.reduce((sum, def) => {
|
||||
return sum + countApi(def);
|
||||
}, 0) +
|
||||
doc.common.reduce((sum, def) => {
|
||||
return sum + countApi(def);
|
||||
}, 0)
|
||||
);
|
||||
}
|
||||
|
||||
function countApi(doc: ApiDeclaration): number {
|
||||
if (!doc.children) return 1;
|
||||
else
|
||||
return (
|
||||
1 +
|
||||
doc.children.reduce((sum, child) => {
|
||||
return sum + countApi(child);
|
||||
}, 0)
|
||||
);
|
||||
}
|
25
packages/kbn-docs-utils/src/api_docs/find_plugins.ts
Normal file
25
packages/kbn-docs-utils/src/api_docs/find_plugins.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import Path from 'path';
|
||||
|
||||
import { getPluginSearchPaths } from '@kbn/config';
|
||||
import { simpleKibanaPlatformPluginDiscovery, REPO_ROOT } from '@kbn/dev-utils';
|
||||
|
||||
export function findPlugins() {
|
||||
const pluginSearchPaths = getPluginSearchPaths({
|
||||
rootDir: REPO_ROOT,
|
||||
oss: false,
|
||||
examples: false,
|
||||
});
|
||||
|
||||
return simpleKibanaPlatformPluginDiscovery(pluginSearchPaths, [
|
||||
// discover "core" as a plugin
|
||||
Path.resolve(REPO_ROOT, 'src/core'),
|
||||
]);
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import Path from 'path';
|
||||
import { KibanaPlatformPlugin, ToolingLog } from '@kbn/dev-utils';
|
||||
import { Project, SourceFile, Node } from 'ts-morph';
|
||||
import { ApiScope } from './types';
|
||||
import { isNamedNode, getSourceFileMatching } from './tsmorph_utils';
|
||||
|
||||
/**
|
||||
* Determines which file in the project to grab nodes from, depending on the plugin and scope, then returns those nodes.
|
||||
*
|
||||
* @param project - TS project.
|
||||
* @param plugin - The plugin we are interested in.
|
||||
* @param scope - The "scope" of the API we want to extract: public, server or common.
|
||||
* @param log - logging utility.
|
||||
*
|
||||
* @return Every publically exported Node from the given plugin and scope (public, server, common).
|
||||
*/
|
||||
export function getDeclarationNodesForPluginScope(
|
||||
project: Project,
|
||||
plugin: KibanaPlatformPlugin,
|
||||
scope: ApiScope,
|
||||
log: ToolingLog
|
||||
): Node[] {
|
||||
const path = Path.join(`${plugin.directory}`, scope.toString(), 'index.ts');
|
||||
const file = getSourceFileMatching(project, path);
|
||||
|
||||
if (file) {
|
||||
return getExportedFileDeclarations(file, log);
|
||||
} else {
|
||||
log.debug(`No file found: ${path}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param source the file we want to extract exported declaration nodes from.
|
||||
* @param log
|
||||
*/
|
||||
function getExportedFileDeclarations(source: SourceFile, log: ToolingLog): Node[] {
|
||||
const nodes: Node[] = [];
|
||||
const exported = source.getExportedDeclarations();
|
||||
|
||||
// Filter out the exported declarations that exist only for the plugin system itself.
|
||||
exported.forEach((val) => {
|
||||
val.forEach((ed) => {
|
||||
const name: string = isNamedNode(ed) ? ed.getName() : '';
|
||||
|
||||
// Every plugin will have an export called "plugin". Don't bother listing
|
||||
// it, it's only for the plugin infrastructure.
|
||||
// Config is also a common export on the server side that is just for the
|
||||
// plugin infrastructure.
|
||||
if (name === 'plugin' || name === 'config') {
|
||||
return;
|
||||
}
|
||||
if (name && name !== '') {
|
||||
nodes.push(ed);
|
||||
} else {
|
||||
log.warning(`API with missing name encountered.`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
log.debug(`Collected ${nodes.length} exports from file ${source.getFilePath()}`);
|
||||
return nodes;
|
||||
}
|
135
packages/kbn-docs-utils/src/api_docs/get_plugin_api.ts
Normal file
135
packages/kbn-docs-utils/src/api_docs/get_plugin_api.ts
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import Path from 'path';
|
||||
import { Node, Project, Type } from 'ts-morph';
|
||||
import { ToolingLog, KibanaPlatformPlugin } from '@kbn/dev-utils';
|
||||
import { ApiScope, Lifecycle } from './types';
|
||||
import { ApiDeclaration, PluginApi } from './types';
|
||||
import { buildApiDeclaration } from './build_api_declarations/build_api_declaration';
|
||||
import { getDeclarationNodesForPluginScope } from './get_declaration_nodes_for_plugin';
|
||||
import { getSourceFileMatching } from './tsmorph_utils';
|
||||
|
||||
/**
|
||||
* Collects all the information neccessary to generate this plugins mdx api file(s).
|
||||
*/
|
||||
export function getPluginApi(
|
||||
project: Project,
|
||||
plugin: KibanaPlatformPlugin,
|
||||
plugins: KibanaPlatformPlugin[],
|
||||
log: ToolingLog
|
||||
): PluginApi {
|
||||
const client = getDeclarations(project, plugin, ApiScope.CLIENT, plugins, log);
|
||||
const server = getDeclarations(project, plugin, ApiScope.SERVER, plugins, log);
|
||||
const common = getDeclarations(project, plugin, ApiScope.COMMON, plugins, log);
|
||||
return {
|
||||
id: plugin.manifest.id,
|
||||
client,
|
||||
server,
|
||||
common,
|
||||
serviceFolders: plugin.manifest.serviceFolders,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns All exported ApiDeclarations for the given plugin and scope (client, server, common), broken into
|
||||
* groups of typescript kinds (functions, classes, interfaces, etc).
|
||||
*/
|
||||
function getDeclarations(
|
||||
project: Project,
|
||||
plugin: KibanaPlatformPlugin,
|
||||
scope: ApiScope,
|
||||
plugins: KibanaPlatformPlugin[],
|
||||
log: ToolingLog
|
||||
): ApiDeclaration[] {
|
||||
const nodes = getDeclarationNodesForPluginScope(project, plugin, scope, log);
|
||||
|
||||
const contractTypes = getContractTypes(project, plugin, scope);
|
||||
|
||||
const declarations = nodes.reduce<ApiDeclaration[]>((acc, node) => {
|
||||
const apiDec = buildApiDeclaration(node, plugins, log, plugin.manifest.id, scope);
|
||||
// Filter out apis with the @internal flag on them.
|
||||
if (!apiDec.tags || apiDec.tags.indexOf('internal') < 0) {
|
||||
// buildApiDeclaration doesn't set the lifecycle, so we set it here.
|
||||
const lifecycle = getLifecycle(node, contractTypes);
|
||||
acc.push({
|
||||
...apiDec,
|
||||
lifecycle,
|
||||
initialIsOpen: lifecycle !== undefined,
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
// We have all the ApiDeclarations, now lets group them by typescript kinds.
|
||||
return declarations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this node is one of the special start or setup contract interface types. We pull these
|
||||
* to the top of the API docs.
|
||||
*
|
||||
* @param node ts-morph node
|
||||
* @param contractTypeNames the start and setup contract interface names
|
||||
* @returns Which, if any, lifecycle contract this node happens to represent.
|
||||
*/
|
||||
function getLifecycle(
|
||||
node: Node,
|
||||
contractTypeNames: { start?: Type; setup?: Type }
|
||||
): Lifecycle | undefined {
|
||||
// Note this logic is not tested if a plugin uses "as",
|
||||
// like export { Setup as MyPluginSetup } from ..."
|
||||
if (contractTypeNames.start && node.getType() === contractTypeNames.start) {
|
||||
return Lifecycle.START;
|
||||
}
|
||||
|
||||
if (contractTypeNames.setup && node.getType() === contractTypeNames.setup) {
|
||||
return Lifecycle.SETUP;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param project
|
||||
* @param plugin the plugin we are interested in.
|
||||
* @param scope Whether we are interested in the client or server plugin contracts.
|
||||
* Common scope will never return anything.
|
||||
* @returns the name of the two types used for Start and Setup contracts, if they
|
||||
* exist and were exported from the plugin class.
|
||||
*/
|
||||
function getContractTypes(
|
||||
project: Project,
|
||||
plugin: KibanaPlatformPlugin,
|
||||
scope: ApiScope
|
||||
): { setup?: Type; start?: Type } {
|
||||
const contractTypes: { setup?: Type; start?: Type } = {};
|
||||
const file = getSourceFileMatching(
|
||||
project,
|
||||
Path.join(`${plugin.directory}`, scope.toString(), 'plugin.ts')
|
||||
);
|
||||
if (file) {
|
||||
file.getClasses().forEach((c) => {
|
||||
c.getImplements().forEach((i) => {
|
||||
let index = 0;
|
||||
i.getType()
|
||||
.getTypeArguments()
|
||||
.forEach((arg) => {
|
||||
// Setup type comes first
|
||||
if (index === 0) {
|
||||
contractTypes.setup = arg;
|
||||
} else if (index === 1) {
|
||||
contractTypes.start = arg;
|
||||
}
|
||||
index++;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
return contractTypes;
|
||||
}
|
9
packages/kbn-docs-utils/src/api_docs/index.ts
Normal file
9
packages/kbn-docs-utils/src/api_docs/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './build_api_docs_cli';
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import Path from 'path';
|
||||
import { Project } from 'ts-morph';
|
||||
import { ToolingLog, KibanaPlatformPlugin } from '@kbn/dev-utils';
|
||||
|
||||
import { PluginApi } from '../types';
|
||||
import { getKibanaPlatformPlugin } from '../tests/kibana_platform_plugin_mock';
|
||||
import { getPluginApi } from '../get_plugin_api';
|
||||
import { splitApisByFolder } from './write_plugin_split_by_folder';
|
||||
|
||||
const log = new ToolingLog({
|
||||
level: 'debug',
|
||||
writeTo: process.stdout,
|
||||
});
|
||||
|
||||
let doc: PluginApi;
|
||||
|
||||
beforeAll(() => {
|
||||
const tsConfigFilePath = Path.resolve(__dirname, '../tests/__fixtures__/src/tsconfig.json');
|
||||
const project = new Project({
|
||||
tsConfigFilePath,
|
||||
});
|
||||
|
||||
expect(project.getSourceFiles().length).toBeGreaterThan(0);
|
||||
|
||||
const pluginA = getKibanaPlatformPlugin('pluginA');
|
||||
pluginA.manifest.serviceFolders = ['foo'];
|
||||
const plugins: KibanaPlatformPlugin[] = [pluginA];
|
||||
|
||||
doc = getPluginApi(project, plugins[0], plugins, log);
|
||||
});
|
||||
|
||||
test('foo service has all exports', () => {
|
||||
expect(doc?.client.length).toBe(33);
|
||||
const split = splitApisByFolder(doc);
|
||||
expect(split.length).toBe(2);
|
||||
|
||||
const fooDoc = split.find((d) => d.id === 'pluginA.foo');
|
||||
const mainDoc = split.find((d) => d.id === 'pluginA');
|
||||
|
||||
expect(fooDoc?.common.length).toBe(1);
|
||||
expect(fooDoc?.client.length).toBe(2);
|
||||
expect(mainDoc?.client.length).toBe(31);
|
||||
});
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/dev-utils';
|
||||
import fs from 'fs';
|
||||
import Path from 'path';
|
||||
import dedent from 'dedent';
|
||||
import { PluginApi, ScopeApi } from '../types';
|
||||
import {
|
||||
countScopeApi,
|
||||
getPluginApiDocId,
|
||||
snakeToCamel,
|
||||
camelToSnake,
|
||||
groupPluginApi,
|
||||
} from '../utils';
|
||||
import { writePluginDocSplitByFolder } from './write_plugin_split_by_folder';
|
||||
|
||||
/**
|
||||
* Converts the plugin doc to mdx and writes it into the file system. If the plugin,
|
||||
* has serviceFolders specified in it's kibana.json, multiple mdx files will be written.
|
||||
*
|
||||
* @param folder The location the mdx files will be written too.
|
||||
* @param doc Contains the information of the plugin that will be written into mdx.
|
||||
* @param log Used for logging debug and error information.
|
||||
*/
|
||||
export function writePluginDocs(folder: string, doc: PluginApi, log: ToolingLog): void {
|
||||
if (doc.serviceFolders) {
|
||||
log.debug(`Splitting plugin ${doc.id}`);
|
||||
writePluginDocSplitByFolder(folder, doc, log);
|
||||
} else {
|
||||
writePluginDoc(folder, doc, log);
|
||||
}
|
||||
}
|
||||
|
||||
function hasPublicApi(doc: PluginApi): boolean {
|
||||
return doc.client.length > 0 || doc.server.length > 0 || doc.common.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the plugin doc to mdx and writes it into the file system. Ignores
|
||||
* the serviceFolders setting. Use {@link writePluginDocs} if you wish to split
|
||||
* the plugin into potentially multiple mdx files.
|
||||
*
|
||||
* @param folder The location the mdx file will be written too.
|
||||
* @param doc Contains the information of the plugin that will be written into mdx.
|
||||
* @param log Used for logging debug and error information.
|
||||
*/
|
||||
export function writePluginDoc(folder: string, doc: PluginApi, log: ToolingLog): void {
|
||||
if (!hasPublicApi(doc)) {
|
||||
log.debug(`${doc.id} does not have a public api. Skipping.`);
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug(`Writing plugin file for ${doc.id}`);
|
||||
|
||||
const fileName = getFileName(doc.id);
|
||||
// Append "obj" to avoid special names in here. 'case' is one in particular that
|
||||
// caused issues.
|
||||
const json = getJsonName(fileName) + 'Obj';
|
||||
let mdx =
|
||||
dedent(`
|
||||
---
|
||||
id: ${getPluginApiDocId(doc.id, log)}
|
||||
slug: /kibana-dev-docs/${doc.id}PluginApi
|
||||
title: ${doc.id}
|
||||
image: https://source.unsplash.com/400x175/?github
|
||||
summary: API docs for the ${doc.id} plugin
|
||||
date: 2020-11-16
|
||||
tags: ['contributor', 'dev', 'apidocs', 'kibana', '${doc.id}']
|
||||
---
|
||||
|
||||
import ${json} from './${fileName}.json';
|
||||
|
||||
`) + '\n\n';
|
||||
|
||||
const scopedDoc = {
|
||||
...doc,
|
||||
client: groupPluginApi(doc.client),
|
||||
common: groupPluginApi(doc.common),
|
||||
server: groupPluginApi(doc.server),
|
||||
};
|
||||
fs.writeFileSync(Path.resolve(folder, fileName + '.json'), JSON.stringify(scopedDoc));
|
||||
|
||||
mdx += scopApiToMdx(scopedDoc.client, 'Client', json, 'client');
|
||||
mdx += scopApiToMdx(scopedDoc.server, 'Server', json, 'server');
|
||||
mdx += scopApiToMdx(scopedDoc.common, 'Common', json, 'common');
|
||||
|
||||
fs.writeFileSync(Path.resolve(folder, fileName + '.mdx'), mdx);
|
||||
}
|
||||
|
||||
function getJsonName(name: string): string {
|
||||
return snakeToCamel(getFileName(name));
|
||||
}
|
||||
|
||||
function getFileName(name: string): string {
|
||||
return camelToSnake(name.replace('.', '_'));
|
||||
}
|
||||
|
||||
function scopApiToMdx(scope: ScopeApi, title: string, json: string, scopeName: string): string {
|
||||
let mdx = '';
|
||||
if (countScopeApi(scope) > 0) {
|
||||
mdx += `## ${title}\n\n`;
|
||||
|
||||
if (scope.setup) {
|
||||
mdx += `### Setup\n`;
|
||||
mdx += `<DocDefinitionList data={[${json}.${scopeName}.setup]}/>\n`;
|
||||
}
|
||||
if (scope.start) {
|
||||
mdx += `### Start\n`;
|
||||
mdx += `<DocDefinitionList data={[${json}.${scopeName}.start]}/>\n`;
|
||||
}
|
||||
if (scope.objects.length > 0) {
|
||||
mdx += `### Objects\n`;
|
||||
mdx += `<DocDefinitionList data={${json}.${scopeName}.objects}/>\n`;
|
||||
}
|
||||
if (scope.functions.length > 0) {
|
||||
mdx += `### Functions\n`;
|
||||
mdx += `<DocDefinitionList data={${json}.${scopeName}.functions}/>\n`;
|
||||
}
|
||||
if (scope.classes.length > 0) {
|
||||
mdx += `### Classes\n`;
|
||||
mdx += `<DocDefinitionList data={${json}.${scopeName}.classes}/>\n`;
|
||||
}
|
||||
if (scope.interfaces.length > 0) {
|
||||
mdx += `### Interfaces\n`;
|
||||
mdx += `<DocDefinitionList data={${json}.${scopeName}.interfaces}/>\n`;
|
||||
}
|
||||
if (scope.enums.length > 0) {
|
||||
mdx += `### Enums\n`;
|
||||
mdx += `<DocDefinitionList data={${json}.${scopeName}.enums}/>\n`;
|
||||
}
|
||||
if (scope.misc.length > 0) {
|
||||
mdx += `### Consts, variables and types\n`;
|
||||
mdx += `<DocDefinitionList data={${json}.${scopeName}.misc}/>\n`;
|
||||
}
|
||||
}
|
||||
return mdx;
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Project } from 'ts-morph';
|
||||
import { ToolingLog, KibanaPlatformPlugin } from '@kbn/dev-utils';
|
||||
import { splitApisByFolder } from './write_plugin_split_by_folder';
|
||||
import { getPluginApi } from '../get_plugin_api';
|
||||
import { getKibanaPlatformPlugin } from '../tests/kibana_platform_plugin_mock';
|
||||
|
||||
const log = new ToolingLog({
|
||||
level: 'debug',
|
||||
writeTo: process.stdout,
|
||||
});
|
||||
|
||||
it('splitApisByFolder test splitting plugin by service folder', () => {
|
||||
const project = new Project({ useInMemoryFileSystem: true });
|
||||
project.createSourceFile(
|
||||
'src/plugins/example/public/index.ts',
|
||||
`
|
||||
import { bar } from './a_service/foo/bar';
|
||||
import { Zed, zed } from './a_service/zed';
|
||||
import { util } from './utils';
|
||||
|
||||
export { bar, Zed, zed, mainFoo, util };
|
||||
`
|
||||
);
|
||||
project.createSourceFile(
|
||||
'src/plugins/example/public/a_service/zed.ts',
|
||||
`export const zed: string = 'hi';
|
||||
export interface Zed = { zed: string }`
|
||||
);
|
||||
project.createSourceFile(
|
||||
'src/plugins/example/public/a_service/foo/bar.ts',
|
||||
`export const bar: string = 'bar';`
|
||||
);
|
||||
project.createSourceFile(
|
||||
'src/plugins/example/public/utils.ts',
|
||||
`export const util: string = 'Util';`
|
||||
);
|
||||
|
||||
const plugin = getKibanaPlatformPlugin('example', '/src/plugins/example');
|
||||
const plugins: KibanaPlatformPlugin[] = [
|
||||
{
|
||||
...plugin,
|
||||
manifest: {
|
||||
...plugin.manifest,
|
||||
serviceFolders: ['a_service'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const doc = getPluginApi(project, plugins[0], plugins, log);
|
||||
const docs = splitApisByFolder(doc);
|
||||
|
||||
// The api at the main level, and one on a service level.
|
||||
expect(docs.length).toBe(2);
|
||||
|
||||
const mainDoc = docs.find((d) => d.id === 'example');
|
||||
|
||||
expect(mainDoc).toBeDefined();
|
||||
|
||||
const serviceDoc = docs.find((d) => d.id === 'example.aService');
|
||||
|
||||
expect(serviceDoc).toBeDefined();
|
||||
|
||||
expect(serviceDoc?.client.length).toBe(3);
|
||||
});
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/dev-utils';
|
||||
import { snakeToCamel } from '../utils';
|
||||
import { PluginApi, ApiDeclaration } from '../types';
|
||||
import { writePluginDoc } from './write_plugin_mdx_docs';
|
||||
|
||||
export function writePluginDocSplitByFolder(folder: string, doc: PluginApi, log: ToolingLog) {
|
||||
const apisByFolder = splitApisByFolder(doc);
|
||||
|
||||
log.debug(`Split ${doc.id} into ${apisByFolder.length} services`);
|
||||
apisByFolder.forEach((docDef) => {
|
||||
writePluginDoc(folder, docDef, log);
|
||||
});
|
||||
}
|
||||
|
||||
export function splitApisByFolder(pluginDoc: PluginApi): PluginApi[] {
|
||||
const pluginDocDefsByFolder: { [key: string]: PluginApi } = {};
|
||||
const mainPluginDocDef = createServicePluginDocDef(pluginDoc);
|
||||
|
||||
pluginDoc.client.forEach((dec: ApiDeclaration) => {
|
||||
addSection(dec, 'client', mainPluginDocDef, pluginDocDefsByFolder, pluginDoc.serviceFolders!);
|
||||
});
|
||||
pluginDoc.server.forEach((dec: ApiDeclaration) => {
|
||||
addSection(dec, 'server', mainPluginDocDef, pluginDocDefsByFolder, pluginDoc.serviceFolders!);
|
||||
});
|
||||
pluginDoc.common.forEach((dec: ApiDeclaration) => {
|
||||
addSection(dec, 'common', mainPluginDocDef, pluginDocDefsByFolder, pluginDoc.serviceFolders!);
|
||||
});
|
||||
|
||||
return [...Object.values(pluginDocDefsByFolder), mainPluginDocDef];
|
||||
}
|
||||
|
||||
function addSection(
|
||||
dec: ApiDeclaration,
|
||||
scope: 'client' | 'server' | 'common',
|
||||
mainPluginDocDef: PluginApi,
|
||||
pluginServices: { [key: string]: PluginApi },
|
||||
serviceFolders: readonly string[]
|
||||
) {
|
||||
const scopeFolder = scope === 'client' ? 'public' : scope;
|
||||
const matchGroup = dec.source.path.match(`.*?\/${scopeFolder}\/([^\/]*?)\/`);
|
||||
const serviceFolderName = matchGroup ? matchGroup[1] : undefined;
|
||||
|
||||
if (serviceFolderName && serviceFolders.find((f) => f === serviceFolderName)) {
|
||||
const service = snakeToCamel(serviceFolderName);
|
||||
if (!pluginServices[service]) {
|
||||
pluginServices[service] = createServicePluginDocDef(mainPluginDocDef, service);
|
||||
}
|
||||
pluginServices[service][scope].push(dec);
|
||||
} else {
|
||||
mainPluginDocDef[scope].push(dec);
|
||||
}
|
||||
}
|
||||
|
||||
function createServicePluginDocDef(pluginDoc: PluginApi, service?: string): PluginApi {
|
||||
return {
|
||||
id: service ? pluginDoc.id + '.' + service : pluginDoc.id,
|
||||
client: [],
|
||||
server: [],
|
||||
common: [],
|
||||
};
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export const commonFoo = 'COMMON VAR!';
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { commonFoo } from './foo';
|
||||
|
||||
export interface ImACommonType {
|
||||
goo: number;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"id": "pluginA",
|
||||
"summary": "This an example plugin for testing the api documentation system",
|
||||
"version": "kibana",
|
||||
"serviceFolders": ["foo"]
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
import { ImAType } from './types';
|
||||
|
||||
/**
|
||||
* An interface with a generic.
|
||||
*/
|
||||
export interface WithGen<T = number> {
|
||||
t: T;
|
||||
}
|
||||
|
||||
export interface AnotherInterface<T> {
|
||||
t: T;
|
||||
}
|
||||
|
||||
export class ExampleClass<T> implements AnotherInterface<T> {
|
||||
/**
|
||||
* This should not be exposed in the docs!
|
||||
*/
|
||||
private privateVar: string;
|
||||
|
||||
public component?: React.ComponentType;
|
||||
|
||||
constructor(public t: T) {
|
||||
this.privateVar = 'hi';
|
||||
}
|
||||
|
||||
/**
|
||||
* an arrow fn on a class.
|
||||
* @param a im a string
|
||||
*/
|
||||
arrowFn = (a: ImAType): ImAType => a;
|
||||
|
||||
/**
|
||||
* A function on a class.
|
||||
* @param a a param
|
||||
*/
|
||||
getVar(a: ImAType) {
|
||||
return this.privateVar;
|
||||
}
|
||||
}
|
||||
|
||||
export class CrazyClass<P extends ImAType = any> extends ExampleClass<WithGen<P>> {}
|
||||
|
||||
/**
|
||||
* This is an example interface so we can see how it appears inside the API
|
||||
* documentation system.
|
||||
*/
|
||||
export interface ExampleInterface extends AnotherInterface<string> {
|
||||
/**
|
||||
* This gets a promise that resolves to a string.
|
||||
*/
|
||||
getAPromiseThatResolvesToString: () => Promise<string>;
|
||||
|
||||
/**
|
||||
* This function takes a generic. It was sometimes being tripped on
|
||||
* and returned as an unknown type with no signature.
|
||||
*/
|
||||
aFnWithGen: <T>(t: T) => void;
|
||||
|
||||
/**
|
||||
* These are not coming back properly.
|
||||
*/
|
||||
aFn(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface that has a react component.
|
||||
*/
|
||||
export interface IReturnAReactComponent {
|
||||
component: React.ComponentType;
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { CrazyClass } from './classes';
|
||||
import { notAnArrowFn } from './fns';
|
||||
import { ImAType } from './types';
|
||||
|
||||
/**
|
||||
* Some of the plugins wrap static exports in an object to create
|
||||
* a namespace like this.
|
||||
*/
|
||||
export const aPretendNamespaceObj = {
|
||||
/**
|
||||
* The docs should show this inline comment.
|
||||
*/
|
||||
notAnArrowFn,
|
||||
|
||||
/**
|
||||
* Should this comment show up?
|
||||
*/
|
||||
aPropertyMisdirection: notAnArrowFn,
|
||||
|
||||
/**
|
||||
* I'm a property inline fun.
|
||||
*/
|
||||
aPropertyInlineFn: (a: ImAType): ImAType => {
|
||||
return a;
|
||||
},
|
||||
|
||||
/**
|
||||
* The only way for this to have a comment is to grab this.
|
||||
*/
|
||||
aPropertyStr: 'Hi',
|
||||
|
||||
/**
|
||||
* Will this nested object have it's children extracted appropriately?
|
||||
*/
|
||||
nestedObj: {
|
||||
foo: 'string',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a complicated union type
|
||||
*/
|
||||
export const aUnionProperty: string | number | (() => string) | CrazyClass = '6';
|
||||
|
||||
/**
|
||||
* This is an array of strings. The type is explicit.
|
||||
*/
|
||||
export const aStrArray: string[] = ['hi', 'bye'];
|
||||
|
||||
/**
|
||||
* This is an array of numbers. The type is implied.
|
||||
*/
|
||||
export const aNumArray = [1, 3, 4];
|
||||
|
||||
/**
|
||||
* A string that says hi to you!
|
||||
*/
|
||||
export const aStr: string = 'hi';
|
||||
|
||||
/**
|
||||
* It's a number. A special number.
|
||||
*/
|
||||
export const aNum = 10;
|
||||
|
||||
/**
|
||||
* I'm a type of string, but more specifically, a literal string type.
|
||||
*/
|
||||
export const literalString = 'HI';
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { TypeWithGeneric, ImAType } from './types';
|
||||
|
||||
/**
|
||||
* This is a non arrow function.
|
||||
*
|
||||
* @param a The letter A
|
||||
* @param b Feed me to the function
|
||||
* @param c So many params
|
||||
* @param d a great param
|
||||
* @param e Another comment
|
||||
* @returns something!
|
||||
*/
|
||||
export function notAnArrowFn(
|
||||
a: string,
|
||||
b: number | undefined,
|
||||
c: TypeWithGeneric<string>,
|
||||
d: ImAType,
|
||||
e?: string
|
||||
): TypeWithGeneric<string> {
|
||||
return ['hi'];
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an arrow function.
|
||||
*
|
||||
* @param a The letter A
|
||||
* @param b Feed me to the function
|
||||
* @param c So many params
|
||||
* @param d a great param
|
||||
* @param e Another comment
|
||||
* @returns something!
|
||||
*/
|
||||
export const arrowFn = (
|
||||
a: string,
|
||||
b: number | undefined,
|
||||
c: TypeWithGeneric<string>,
|
||||
d: ImAType,
|
||||
e?: string
|
||||
): TypeWithGeneric<string> => {
|
||||
return ['hi'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Who would write such a complicated function?? Ewwww.
|
||||
*
|
||||
* According to https://jsdoc.app/tags-param.html#parameters-with-properties,
|
||||
* this is how destructured arguements should be commented.
|
||||
*
|
||||
* @param obj A very crazy parameter that is destructured when passing in.
|
||||
* @param objWithFn Im an object with a function. Destructed!
|
||||
* @param objWithFn.fn A fn.
|
||||
* @param objWithStr Im an object with a string. Destructed!
|
||||
* @param objWithStr.str A str.
|
||||
*
|
||||
* @returns I have no idea.
|
||||
*
|
||||
*/
|
||||
export const crazyFunction = (
|
||||
obj: { hi: string },
|
||||
{ fn }: { fn: (foo: { param: string }) => number },
|
||||
{ str }: { str: string }
|
||||
) => () => () => fn({ param: str });
|
||||
|
||||
interface ImNotExported {
|
||||
foo: string;
|
||||
}
|
||||
|
||||
export const fnWithNonExportedRef = (a: ImNotExported) => 'shi';
|
||||
|
||||
export type NotAnArrowFnType = typeof notAnArrowFn;
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export const doTheFooFnThing = () => {};
|
||||
|
||||
export type FooType = () => 'foo';
|
||||
|
||||
export type ImNotExportedFromIndex = () => { bar: string };
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PluginA, Setup, Start, SearchSpec } from './plugin';
|
||||
export { Setup, Start, SearchSpec };
|
||||
|
||||
export { doTheFooFnThing, FooType } from './foo';
|
||||
|
||||
export * from './fns';
|
||||
export * from './classes';
|
||||
export * from './const_vars';
|
||||
export * from './types';
|
||||
|
||||
export const imAnAny: any = 'hi';
|
||||
export const imAnUnknown: unknown = 'hi';
|
||||
|
||||
export function plugin() {
|
||||
return new PluginA();
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
// The logic for grabbing Setup and Start types relies on implementing an
|
||||
// interface with at least two type args. Since the test code isn't adding
|
||||
// every import file, use this mock, otherwise it won't have the type and will
|
||||
// fail.
|
||||
interface PluginMock<Sp, St> {
|
||||
setup(): Sp;
|
||||
start(): St;
|
||||
}
|
||||
|
||||
/**
|
||||
* The SearchSpec interface contains settings for creating a new SearchService, like
|
||||
* username and password.
|
||||
*/
|
||||
export interface SearchSpec {
|
||||
/**
|
||||
* Stores the username. Duh,
|
||||
*/
|
||||
username: string;
|
||||
/**
|
||||
* Stores the password. I hope it's encrypted!
|
||||
*/
|
||||
password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of search language.
|
||||
*/
|
||||
export enum SearchLanguage {
|
||||
/**
|
||||
* The SQL SearchLanguage type
|
||||
*/
|
||||
SQL,
|
||||
/**
|
||||
* The EQL SearchLanguage type. Support sequences.
|
||||
*/
|
||||
EQL,
|
||||
/**
|
||||
* The ES DSL SearchLanguage type. It's the default.
|
||||
*/
|
||||
ES_DSL,
|
||||
}
|
||||
|
||||
/**
|
||||
* Access start functionality from your plugin's start function by adding the example
|
||||
* plugin as a dependency.
|
||||
*
|
||||
* ```ts
|
||||
* Class MyPlugin {
|
||||
* start(core: CoreDependencies, { example }: PluginDependencies) {
|
||||
* // Here you can access this functionality.
|
||||
* example.getSearchLanguage();
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface Start {
|
||||
/**
|
||||
* @returns The currently selected {@link SearchLanguage}
|
||||
*/
|
||||
getSearchLanguage: () => SearchLanguage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access setup functionality from your plugin's setup function by adding the example
|
||||
* plugin as a dependency.
|
||||
*
|
||||
* ```ts
|
||||
* Class MyPlugin {
|
||||
* setup(core: CoreDependencies, { example }: PluginDependencies) {
|
||||
* // Here you can access this functionality.
|
||||
* example.getSearchService();
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface Setup {
|
||||
/**
|
||||
* A factory function that returns a new instance of Foo based
|
||||
* on the spec. We aren't sure if this is a good function so it's marked
|
||||
* beta. That should be clear in the docs because of the js doc tag.
|
||||
*
|
||||
* @param searchSpec Provide the settings neccessary to create a new Search Service
|
||||
*
|
||||
* @returns the id of the search service.
|
||||
*
|
||||
* @beta
|
||||
*/
|
||||
getSearchService: (searchSpec: SearchSpec) => string;
|
||||
|
||||
/**
|
||||
* This uses an inlined object type rather than referencing an exported type, which is discouraged.
|
||||
* prefer the way {@link getSearchService} is typed.
|
||||
*
|
||||
* @param searchSpec Provide the settings neccessary to create a new Search Service
|
||||
*/
|
||||
getSearchService2: (searchSpec: { username: string; password: string }) => string;
|
||||
|
||||
/**
|
||||
* This function does the thing and it's so good at it! But we decided to deprecate it
|
||||
* anyway. I hope that's clear to developers in the docs!
|
||||
*
|
||||
* @param thingOne Thing one comment
|
||||
* @param thingTwo ThingTwo comment
|
||||
* @param thingThree Thing three is an object with a nested var
|
||||
*
|
||||
* @deprecated
|
||||
*
|
||||
*/
|
||||
doTheThing: (thingOne: number, thingTwo: string, thingThree: { nestedVar: number }) => void;
|
||||
|
||||
/**
|
||||
* Who would write such a complicated function?? Ew, how will the obj parameter appear in docs?
|
||||
*
|
||||
* @param obj A funky parameter.
|
||||
*
|
||||
* @returns It's hard to tell but I think this returns a function that returns an object with a
|
||||
* property that is a function that returns a string. Whoa.
|
||||
*
|
||||
*/
|
||||
fnWithInlineParams: (obj: {
|
||||
fn: (foo: { param: string }) => number;
|
||||
}) => () => { retFoo: () => string };
|
||||
|
||||
/**
|
||||
* Hi, I'm a comment for an id string!
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This comment won't show up in the API docs.
|
||||
*/
|
||||
function getSearchService() {
|
||||
return 'hi';
|
||||
}
|
||||
|
||||
function fnWithInlineParams() {
|
||||
return () => ({
|
||||
retFoo: () => 'hi',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The example search plugin is a fake plugin that is built only to test our api documentation system.
|
||||
*
|
||||
*/
|
||||
export class PluginA implements PluginMock<Setup, Start> {
|
||||
setup() {
|
||||
return {
|
||||
// Don't put comments here - they won't show up. What's here shouldn't matter because
|
||||
// the API documentation system works off the type `Setup`.
|
||||
doTheThing: () => {},
|
||||
fnWithInlineParams,
|
||||
getSearchService,
|
||||
getSearchService2: getSearchService,
|
||||
registerSearch: () => {},
|
||||
id: '123',
|
||||
};
|
||||
}
|
||||
|
||||
start() {
|
||||
return { getSearchLanguage: () => SearchLanguage.EQL };
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ImACommonType } from '../common';
|
||||
import { FooType, ImNotExportedFromIndex } from './foo';
|
||||
|
||||
/**
|
||||
* How should a potentially undefined type show up.
|
||||
*/
|
||||
export type StringOrUndefinedType = string | undefined;
|
||||
|
||||
export type TypeWithGeneric<T> = T[];
|
||||
|
||||
export type ImAType = string | number | TypeWithGeneric<string> | FooType | ImACommonType;
|
||||
|
||||
/**
|
||||
* This is a type that defines a function.
|
||||
*
|
||||
* @param t This is a generic T type. It can be anything.
|
||||
*/
|
||||
export type FnWithGeneric = <T>(t: T) => TypeWithGeneric<T>;
|
||||
|
||||
/**
|
||||
* Comments on enums.
|
||||
*/
|
||||
export enum DayOfWeek {
|
||||
THURSDAY,
|
||||
FRIDAY, // How about this comment, hmmm?
|
||||
SATURDAY,
|
||||
}
|
||||
|
||||
/**
|
||||
* Calling node.getSymbol().getDeclarations() will return > 1 declaration.
|
||||
*/
|
||||
export type MultipleDeclarationsType = TypeWithGeneric<typeof DayOfWeek>;
|
||||
|
||||
export type IRefANotExportedType = ImNotExportedFromIndex | { zed: 'hi' };
|
||||
export interface ImAnObject {
|
||||
foo: FnWithGeneric;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"incremental": false,
|
||||
"strictNullChecks": true,
|
||||
},
|
||||
"include": ["./**/*"]
|
||||
}
|
397
packages/kbn-docs-utils/src/api_docs/tests/api_doc_suite.test.ts
Normal file
397
packages/kbn-docs-utils/src/api_docs/tests/api_doc_suite.test.ts
Normal file
|
@ -0,0 +1,397 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import Path from 'path';
|
||||
|
||||
import { Project } from 'ts-morph';
|
||||
import { ToolingLog, KibanaPlatformPlugin } from '@kbn/dev-utils';
|
||||
|
||||
import { writePluginDocs } from '../mdx/write_plugin_mdx_docs';
|
||||
import { ApiDeclaration, PluginApi, Reference, TextWithLinks, TypeKind } from '../types';
|
||||
import { getKibanaPlatformPlugin } from './kibana_platform_plugin_mock';
|
||||
import { getPluginApi } from '../get_plugin_api';
|
||||
import { groupPluginApi } from '../utils';
|
||||
|
||||
const log = new ToolingLog({
|
||||
level: 'debug',
|
||||
writeTo: process.stdout,
|
||||
});
|
||||
|
||||
let doc: PluginApi;
|
||||
let mdxOutputFolder: string;
|
||||
|
||||
function linkCount(signature: TextWithLinks): number {
|
||||
return signature.reduce((cnt, next) => (typeof next === 'string' ? cnt : cnt + 1), 0);
|
||||
}
|
||||
|
||||
function fnIsCorrect(fn: ApiDeclaration | undefined) {
|
||||
expect(fn).toBeDefined();
|
||||
expect(fn?.type).toBe(TypeKind.FunctionKind);
|
||||
// The signature should contain a link to ExampleInterface param.
|
||||
expect(fn?.signature).toBeDefined();
|
||||
expect(linkCount(fn!.signature!)).toBe(3);
|
||||
|
||||
expect(fn?.children!.length).toBe(5);
|
||||
expect(fn?.returnComment!.length).toBe(1);
|
||||
|
||||
const p1 = fn?.children!.find((c) => c.label === 'a');
|
||||
expect(p1).toBeDefined();
|
||||
expect(p1!.type).toBe(TypeKind.StringKind);
|
||||
expect(p1!.isRequired).toBe(true);
|
||||
expect(p1!.signature?.length).toBe(1);
|
||||
expect(linkCount(p1!.signature!)).toBe(0);
|
||||
|
||||
const p2 = fn?.children!.find((c) => c.label === 'b');
|
||||
expect(p2).toBeDefined();
|
||||
expect(p2!.isRequired).toBe(false);
|
||||
expect(p2!.type).toBe(TypeKind.NumberKind);
|
||||
expect(p2!.signature?.length).toBe(1);
|
||||
expect(linkCount(p2!.signature!)).toBe(0);
|
||||
|
||||
const p3 = fn?.children!.find((c) => c.label === 'c');
|
||||
expect(p3).toBeDefined();
|
||||
expect(p3!.isRequired).toBe(true);
|
||||
expect(p3!.type).toBe(TypeKind.ArrayKind);
|
||||
expect(linkCount(p3!.signature!)).toBe(1);
|
||||
|
||||
const p4 = fn?.children!.find((c) => c.label === 'd');
|
||||
expect(p4).toBeDefined();
|
||||
expect(p4!.isRequired).toBe(true);
|
||||
expect(p4!.type).toBe(TypeKind.CompoundTypeKind);
|
||||
expect(p4!.signature?.length).toBe(1);
|
||||
expect(linkCount(p4!.signature!)).toBe(1);
|
||||
|
||||
const p5 = fn?.children!.find((c) => c.label === 'e');
|
||||
expect(p5).toBeDefined();
|
||||
expect(p5!.isRequired).toBe(false);
|
||||
expect(p5!.type).toBe(TypeKind.StringKind);
|
||||
expect(p5!.signature?.length).toBe(1);
|
||||
expect(linkCount(p5!.signature!)).toBe(0);
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
const tsConfigFilePath = Path.resolve(__dirname, '__fixtures__/src/tsconfig.json');
|
||||
const project = new Project({
|
||||
tsConfigFilePath,
|
||||
});
|
||||
|
||||
expect(project.getSourceFiles().length).toBeGreaterThan(0);
|
||||
|
||||
const pluginA = getKibanaPlatformPlugin('pluginA');
|
||||
pluginA.manifest.serviceFolders = ['foo'];
|
||||
const plugins: KibanaPlatformPlugin[] = [pluginA];
|
||||
|
||||
doc = getPluginApi(project, plugins[0], plugins, log);
|
||||
|
||||
mdxOutputFolder = Path.resolve(__dirname, 'snapshots');
|
||||
writePluginDocs(mdxOutputFolder, doc, log);
|
||||
});
|
||||
|
||||
it('Setup type is extracted', () => {
|
||||
const grouped = groupPluginApi(doc.client);
|
||||
expect(grouped.setup).toBeDefined();
|
||||
});
|
||||
|
||||
it('service mdx file was created', () => {
|
||||
expect(fs.existsSync(Path.resolve(mdxOutputFolder, 'plugin_a_foo.mdx'))).toBe(true);
|
||||
});
|
||||
|
||||
it('Setup type has comment', () => {
|
||||
const grouped = groupPluginApi(doc.client);
|
||||
expect(grouped.setup!.description).toBeDefined();
|
||||
expect(grouped.setup!.description).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"
|
||||
Access setup functionality from your plugin's setup function by adding the example
|
||||
plugin as a dependency.
|
||||
|
||||
\`\`\`ts
|
||||
Class MyPlugin {
|
||||
setup(core: CoreDependencies, { example }: PluginDependencies) {
|
||||
// Here you can access this functionality.
|
||||
example.getSearchService();
|
||||
}
|
||||
}
|
||||
\`\`\`",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('const exported from common folder is correct', () => {
|
||||
const fooConst = doc.common.find((c) => c.label === 'commonFoo');
|
||||
expect(fooConst).toBeDefined();
|
||||
|
||||
expect(fooConst!.source.path.replace(Path.sep, '/')).toContain(
|
||||
'src/plugin_a/common/foo/index.ts'
|
||||
);
|
||||
expect(fooConst!.signature![0]).toBe('"COMMON VAR!"');
|
||||
});
|
||||
|
||||
describe('functions', () => {
|
||||
it('function referencing missing type has link removed', () => {
|
||||
const fn = doc.client.find((c) => c.label === 'fnWithNonExportedRef');
|
||||
expect(linkCount(fn?.signature!)).toBe(0);
|
||||
});
|
||||
it('arrow function is exported correctly', () => {
|
||||
const fn = doc.client.find((c) => c.label === 'arrowFn');
|
||||
// Using the same data as the not an arrow function so this is refactored.
|
||||
fnIsCorrect(fn);
|
||||
});
|
||||
|
||||
it('non arrow function is exported correctly', () => {
|
||||
const fn = doc.client.find((c) => c.label === 'notAnArrowFn');
|
||||
// Using the same data as the arrow function so this is refactored.
|
||||
fnIsCorrect(fn);
|
||||
});
|
||||
|
||||
it('crazyFunction is typed correctly', () => {
|
||||
const fn = doc.client!.find((c) => c.label === 'crazyFunction');
|
||||
|
||||
expect(fn).toBeDefined();
|
||||
|
||||
const obj = fn?.children?.find((c) => c.label === 'obj');
|
||||
expect(obj).toBeDefined();
|
||||
expect(obj!.children?.length).toBe(1);
|
||||
|
||||
const hi = obj?.children?.find((c) => c.label === 'hi');
|
||||
expect(hi).toBeDefined();
|
||||
|
||||
const obj2 = fn?.children?.find((c) => c.label === '{ fn }');
|
||||
expect(obj2).toBeDefined();
|
||||
expect(obj2!.children?.length).toBe(1);
|
||||
|
||||
const fn2 = obj2?.children?.find((c) => c.label === 'fn');
|
||||
expect(fn2).toBeDefined();
|
||||
expect(fn2?.type).toBe(TypeKind.FunctionKind);
|
||||
});
|
||||
});
|
||||
|
||||
describe('objects', () => {
|
||||
it('Object exported correctly', () => {
|
||||
const obj = doc.client.find((c) => c.label === 'aPretendNamespaceObj');
|
||||
expect(obj).toBeDefined();
|
||||
|
||||
const fn = obj?.children?.find((c) => c.label === 'notAnArrowFn');
|
||||
expect(fn?.signature).toBeDefined();
|
||||
// Should just be typeof notAnArrowFn.
|
||||
expect(linkCount(fn?.signature!)).toBe(1);
|
||||
// Comment should be the inline one.
|
||||
expect(fn?.description).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"/**
|
||||
* The docs should show this inline comment.
|
||||
*/",
|
||||
]
|
||||
`);
|
||||
|
||||
const fn2 = obj?.children?.find((c) => c.label === 'aPropertyInlineFn');
|
||||
expect(fn2?.signature).toBeDefined();
|
||||
// Should include 2 links to ImAType
|
||||
expect(linkCount(fn2?.signature!)).toBe(2);
|
||||
expect(fn2?.children).toBeDefined();
|
||||
|
||||
const nestedObj = obj?.children?.find((c) => c.label === 'nestedObj');
|
||||
// We aren't giving objects a signature. The children should contain all the information.
|
||||
expect(nestedObj?.signature).toBeUndefined();
|
||||
expect(nestedObj?.children).toBeDefined();
|
||||
expect(nestedObj?.type).toBe(TypeKind.ObjectKind);
|
||||
const foo = nestedObj?.children?.find((c) => c.label === 'foo');
|
||||
expect(foo?.type).toBe(TypeKind.StringKind);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Misc types', () => {
|
||||
it('Explicitly typed array is returned with the correct type', () => {
|
||||
const aStrArray = doc.client.find((c) => c.label === 'aStrArray');
|
||||
expect(aStrArray).toBeDefined();
|
||||
expect(aStrArray?.type).toBe(TypeKind.ArrayKind);
|
||||
});
|
||||
|
||||
it('Implicitly typed array is returned with the correct type', () => {
|
||||
const aNumArray = doc.client.find((c) => c.label === 'aNumArray');
|
||||
expect(aNumArray).toBeDefined();
|
||||
expect(aNumArray?.type).toBe(TypeKind.ArrayKind);
|
||||
});
|
||||
|
||||
it('Explicitly typed string is returned with the correct type', () => {
|
||||
const aStr = doc.client.find((c) => c.label === 'aStr');
|
||||
expect(aStr).toBeDefined();
|
||||
expect(aStr?.type).toBe(TypeKind.StringKind);
|
||||
// signature would be the same as type, so it should be removed.
|
||||
expect(aStr?.signature).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Implicitly typed number is returned with the correct type', () => {
|
||||
const aNum = doc.client.find((c) => c.label === 'aNum');
|
||||
expect(aNum).toBeDefined();
|
||||
expect(aNum?.type).toBe(TypeKind.NumberKind);
|
||||
});
|
||||
|
||||
it('aUnionProperty is exported as a CompoundType with a call signature', () => {
|
||||
const prop = doc.client.find((c) => c.label === 'aUnionProperty');
|
||||
expect(prop).toBeDefined();
|
||||
expect(prop?.type).toBe(TypeKind.CompoundTypeKind);
|
||||
expect(linkCount(prop?.signature!)).toBe(1);
|
||||
});
|
||||
|
||||
it('Function type is exported correctly', () => {
|
||||
const fnType = doc.client.find((c) => c.label === 'FnWithGeneric');
|
||||
expect(fnType).toBeDefined();
|
||||
expect(fnType?.type).toBe(TypeKind.TypeKind);
|
||||
expect(fnType?.signature!).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"(t: T) => ",
|
||||
Object {
|
||||
"docId": "kibPluginAPluginApi",
|
||||
"pluginId": "pluginA",
|
||||
"scope": "public",
|
||||
"section": "def-public.TypeWithGeneric",
|
||||
"text": "TypeWithGeneric",
|
||||
},
|
||||
"<T>",
|
||||
]
|
||||
`);
|
||||
expect(linkCount(fnType?.signature!)).toBe(1);
|
||||
});
|
||||
|
||||
it('Union type is exported correctly', () => {
|
||||
const type = doc.client.find((c) => c.label === 'ImAType');
|
||||
expect(type).toBeDefined();
|
||||
expect(type?.type).toBe(TypeKind.TypeKind);
|
||||
expect(type?.signature).toBeDefined();
|
||||
expect(type?.signature!).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"string | number | ",
|
||||
Object {
|
||||
"docId": "kibPluginAFooPluginApi",
|
||||
"pluginId": "pluginA",
|
||||
"scope": "public",
|
||||
"section": "def-public.FooType",
|
||||
"text": "FooType",
|
||||
},
|
||||
" | ",
|
||||
Object {
|
||||
"docId": "kibPluginAPluginApi",
|
||||
"pluginId": "pluginA",
|
||||
"scope": "public",
|
||||
"section": "def-public.TypeWithGeneric",
|
||||
"text": "TypeWithGeneric",
|
||||
},
|
||||
"<string> | ",
|
||||
Object {
|
||||
"docId": "kibPluginAPluginApi",
|
||||
"pluginId": "pluginA",
|
||||
"scope": "common",
|
||||
"section": "def-common.ImACommonType",
|
||||
"text": "ImACommonType",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(linkCount(type?.signature!)).toBe(3);
|
||||
expect((type!.signature![1] as Reference).docId).toBe('kibPluginAFooPluginApi');
|
||||
});
|
||||
});
|
||||
|
||||
describe('interfaces and classes', () => {
|
||||
it('Basic interface exported correctly', () => {
|
||||
const anInterface = doc.client.find((c) => c.label === 'IReturnAReactComponent');
|
||||
expect(anInterface).toBeDefined();
|
||||
|
||||
// Make sure it doesn't include a self referential link.
|
||||
expect(anInterface?.signature).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Interface which extends exported correctly', () => {
|
||||
const exampleInterface = doc.client.find((c) => c.label === 'ExampleInterface');
|
||||
expect(exampleInterface).toBeDefined();
|
||||
expect(exampleInterface?.signature).toBeDefined();
|
||||
expect(exampleInterface?.type).toBe(TypeKind.InterfaceKind);
|
||||
|
||||
expect(linkCount(exampleInterface?.signature!)).toBe(2);
|
||||
|
||||
// TODO: uncomment if the bug is fixed.
|
||||
// This is wrong, the link should be to `AnotherInterface`
|
||||
// Another bug, this link is not being captured.
|
||||
expect(exampleInterface?.signature).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"docId": "kibPluginAPluginApi",
|
||||
"pluginId": "pluginA",
|
||||
"scope": "public",
|
||||
"section": "def-public.ExampleInterface",
|
||||
"text": "ExampleInterface",
|
||||
},
|
||||
" extends ",
|
||||
Object {
|
||||
"docId": "kibPluginAPluginApi",
|
||||
"pluginId": "pluginA",
|
||||
"scope": "public",
|
||||
"section": "def-public.AnotherInterface",
|
||||
"text": "AnotherInterface",
|
||||
},
|
||||
"<string>",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('Non arrow function on interface is exported as function type', () => {
|
||||
const exampleInterface = doc.client.find((c) => c.label === 'ExampleInterface');
|
||||
expect(exampleInterface).toBeDefined();
|
||||
|
||||
const fn = exampleInterface!.children?.find((c) => c.label === 'aFn');
|
||||
expect(fn).toBeDefined();
|
||||
expect(fn?.type).toBe(TypeKind.FunctionKind);
|
||||
});
|
||||
|
||||
it('Class exported correctly', () => {
|
||||
const clss = doc.client.find((c) => c.label === 'CrazyClass');
|
||||
expect(clss).toBeDefined();
|
||||
expect(clss?.signature).toBeDefined();
|
||||
expect(clss?.type).toBe(TypeKind.ClassKind);
|
||||
expect(clss?.signature).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"docId": "kibPluginAPluginApi",
|
||||
"pluginId": "pluginA",
|
||||
"scope": "public",
|
||||
"section": "def-public.CrazyClass",
|
||||
"text": "CrazyClass",
|
||||
},
|
||||
"<P> extends ",
|
||||
Object {
|
||||
"docId": "kibPluginAPluginApi",
|
||||
"pluginId": "pluginA",
|
||||
"scope": "public",
|
||||
"section": "def-public.ExampleClass",
|
||||
"text": "ExampleClass",
|
||||
},
|
||||
"<",
|
||||
Object {
|
||||
"docId": "kibPluginAPluginApi",
|
||||
"pluginId": "pluginA",
|
||||
"scope": "public",
|
||||
"section": "def-public.WithGen",
|
||||
"text": "WithGen",
|
||||
},
|
||||
"<P>>",
|
||||
]
|
||||
`);
|
||||
expect(linkCount(clss?.signature!)).toBe(3);
|
||||
});
|
||||
|
||||
it('Function with generic inside interface is exported with function type', () => {
|
||||
const exampleInterface = doc.client.find((c) => c.label === 'ExampleInterface');
|
||||
expect(exampleInterface).toBeDefined();
|
||||
|
||||
const fnWithGeneric = exampleInterface?.children?.find((c) => c.label === 'aFnWithGen');
|
||||
expect(fnWithGeneric).toBeDefined();
|
||||
expect(fnWithGeneric?.type).toBe(TypeKind.FunctionKind);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { KibanaPlatformPlugin } from '@kbn/dev-utils';
|
||||
import Path from 'path';
|
||||
|
||||
export function getKibanaPlatformPlugin(id: string, dir?: string): KibanaPlatformPlugin {
|
||||
const directory = dir ?? Path.resolve(__dirname, '__fixtures__/src/plugin_a');
|
||||
return {
|
||||
manifest: {
|
||||
id,
|
||||
ui: true,
|
||||
server: true,
|
||||
kibanaVersion: '1',
|
||||
version: '1',
|
||||
serviceFolders: [],
|
||||
requiredPlugins: [],
|
||||
requiredBundles: [],
|
||||
optionalPlugins: [],
|
||||
extraPublicDirs: [],
|
||||
},
|
||||
directory,
|
||||
manifestPath: Path.resolve(directory, 'kibana.json'),
|
||||
};
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
id: kibPluginAPluginApi
|
||||
slug: /kibana-dev-docs/pluginAPluginApi
|
||||
title: pluginA
|
||||
image: https://source.unsplash.com/400x175/?github
|
||||
summary: API docs for the pluginA plugin
|
||||
date: 2020-11-16
|
||||
tags: ['contributor', 'dev', 'apidocs', 'kibana', 'pluginA']
|
||||
---
|
||||
|
||||
import pluginAObj from './plugin_a.json';
|
||||
|
||||
## Client
|
||||
|
||||
### Setup
|
||||
<DocDefinitionList data={[pluginAObj.client.setup]}/>
|
||||
### Start
|
||||
<DocDefinitionList data={[pluginAObj.client.start]}/>
|
||||
### Objects
|
||||
<DocDefinitionList data={pluginAObj.client.objects}/>
|
||||
### Functions
|
||||
<DocDefinitionList data={pluginAObj.client.functions}/>
|
||||
### Classes
|
||||
<DocDefinitionList data={pluginAObj.client.classes}/>
|
||||
### Interfaces
|
||||
<DocDefinitionList data={pluginAObj.client.interfaces}/>
|
||||
### Enums
|
||||
<DocDefinitionList data={pluginAObj.client.enums}/>
|
||||
### Consts, variables and types
|
||||
<DocDefinitionList data={pluginAObj.client.misc}/>
|
||||
## Common
|
||||
|
||||
### Interfaces
|
||||
<DocDefinitionList data={pluginAObj.common.interfaces}/>
|
|
@ -0,0 +1 @@
|
|||
{"id":"pluginA.foo","client":{"classes":[],"functions":[{"id":"def-public.doTheFooFnThing","type":"Function","children":[],"signature":["() => void"],"description":[],"label":"doTheFooFnThing","source":{"path":"/packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/foo/index.ts","lineNumber":9,"link":"https://github.com/elastic/kibana/tree/master/packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/foo/index.ts#L9"},"returnComment":[],"initialIsOpen":false}],"interfaces":[],"enums":[],"misc":[{"id":"def-public.FooType","type":"Type","label":"FooType","description":[],"source":{"path":"/packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/foo/index.ts","lineNumber":11,"link":"https://github.com/elastic/kibana/tree/master/packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/foo/index.ts#L11"},"signature":["() => \"foo\""],"initialIsOpen":false}],"objects":[]},"server":{"classes":[],"functions":[],"interfaces":[],"enums":[],"misc":[],"objects":[]},"common":{"classes":[],"functions":[],"interfaces":[],"enums":[],"misc":[{"id":"def-common.commonFoo","type":"string","label":"commonFoo","description":[],"source":{"path":"/packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/common/foo/index.ts","lineNumber":9,"link":"https://github.com/elastic/kibana/tree/master/packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/common/foo/index.ts#L9"},"signature":["\"COMMON VAR!\""],"initialIsOpen":false}],"objects":[]}}
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: kibPluginAFooPluginApi
|
||||
slug: /kibana-dev-docs/pluginA.fooPluginApi
|
||||
title: pluginA.foo
|
||||
image: https://source.unsplash.com/400x175/?github
|
||||
summary: API docs for the pluginA.foo plugin
|
||||
date: 2020-11-16
|
||||
tags: ['contributor', 'dev', 'apidocs', 'kibana', 'pluginA.foo']
|
||||
---
|
||||
|
||||
import pluginAFooObj from './plugin_a_foo.json';
|
||||
|
||||
## Client
|
||||
|
||||
### Functions
|
||||
<DocDefinitionList data={pluginAFooObj.client.functions}/>
|
||||
### Consts, variables and types
|
||||
<DocDefinitionList data={pluginAFooObj.client.misc}/>
|
||||
## Common
|
||||
|
||||
### Consts, variables and types
|
||||
<DocDefinitionList data={pluginAFooObj.common.misc}/>
|
38
packages/kbn-docs-utils/src/api_docs/tsmorph_utils.ts
Normal file
38
packages/kbn-docs-utils/src/api_docs/tsmorph_utils.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Node, SourceFile, Project } from 'ts-morph';
|
||||
|
||||
export interface NamedNode extends Node {
|
||||
getName(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ts-morph has a Node.isNamedNode fn but it isn't returning true for all types
|
||||
* that will have node.getName.
|
||||
*/
|
||||
export function isNamedNode(node: Node | NamedNode): node is NamedNode {
|
||||
return (node as NamedNode).getName !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to find a source file at a given location. Used to extract
|
||||
* index.ts files at a given scope.
|
||||
*
|
||||
* @param project The ts morph project which contains all the source files
|
||||
* @param absolutePath The absolute path of the file we want to find
|
||||
* @returns a source file that exists at the location of the relative path.
|
||||
*/
|
||||
export function getSourceFileMatching(
|
||||
project: Project,
|
||||
absolutePath: string
|
||||
): SourceFile | undefined {
|
||||
return project.getSourceFiles().find((file) => {
|
||||
return file.getFilePath().startsWith(absolutePath);
|
||||
});
|
||||
}
|
200
packages/kbn-docs-utils/src/api_docs/types.ts
Normal file
200
packages/kbn-docs-utils/src/api_docs/types.ts
Normal file
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export interface AnchorLink {
|
||||
/**
|
||||
* The plugin that contains the API being referenced.
|
||||
*/
|
||||
pluginName: string;
|
||||
/**
|
||||
* It's possible the client and the server both emit an API with
|
||||
* the same name so we need scope in here to add uniqueness.
|
||||
*/
|
||||
scope: ApiScope;
|
||||
/**
|
||||
* The name of the api.
|
||||
*/
|
||||
apiName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The kinds of typescript types we want to show in the docs. `Unknown` is used if
|
||||
* we aren't accounting for a particular type. See {@link getPropertyTypeKind}
|
||||
*/
|
||||
export enum TypeKind {
|
||||
ClassKind = 'Class',
|
||||
FunctionKind = 'Function',
|
||||
ObjectKind = 'Object',
|
||||
EnumKind = 'Enum',
|
||||
InterfaceKind = 'Interface',
|
||||
/**
|
||||
* Maps to the typescript syntax kind `TypeReferences`. For example,
|
||||
* export type FooFn = () => string will be a TypeKind, not a FunctionKind.
|
||||
*/
|
||||
TypeKind = 'Type',
|
||||
/**
|
||||
* Uncategorized is used if a type is encountered that isn't handled.
|
||||
*/
|
||||
Uncategorized = 'Uncategorized',
|
||||
UnknownKind = 'Unknown', // Maps to the unknown typescript type
|
||||
AnyKind = 'Any', // Maps to the any typescript type
|
||||
StringKind = 'string',
|
||||
NumberKind = 'number',
|
||||
BooleanKind = 'boolean',
|
||||
ArrayKind = 'Array',
|
||||
/**
|
||||
* This will cover things like string | number, or A & B, for lack of something better to put here.
|
||||
*/
|
||||
CompoundTypeKind = 'CompoundType',
|
||||
}
|
||||
|
||||
export interface ScopeApi {
|
||||
setup?: ApiDeclaration;
|
||||
start?: ApiDeclaration;
|
||||
functions: ApiDeclaration[];
|
||||
objects: ApiDeclaration[];
|
||||
classes: ApiDeclaration[];
|
||||
interfaces: ApiDeclaration[];
|
||||
enums: ApiDeclaration[];
|
||||
misc: ApiDeclaration[];
|
||||
}
|
||||
|
||||
export interface PluginApi {
|
||||
id: string;
|
||||
serviceFolders?: readonly string[];
|
||||
client: ApiDeclaration[];
|
||||
server: ApiDeclaration[];
|
||||
common: ApiDeclaration[];
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used for displaying code or comments that may contain reference links. For example, a function
|
||||
* signature that is `(a: import("src/plugin_b").Bar) => void` will be parsed into the following Array:
|
||||
*
|
||||
* ```ts
|
||||
* [
|
||||
* '(a: ',
|
||||
* { docId: 'pluginB', section: 'Bar', text: 'Bar' },
|
||||
* ') => void'
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* This is then used to render text with nested DocLinks so it looks like this:
|
||||
*
|
||||
* `(a: => <DocLink docId="pluginB" section="Bar" text="Bar"/>) => void`
|
||||
*/
|
||||
export type TextWithLinks = Array<string | Reference>;
|
||||
|
||||
/**
|
||||
* The information neccessary to build a DocLink.
|
||||
*/
|
||||
export interface Reference {
|
||||
pluginId: string;
|
||||
scope: ApiScope;
|
||||
docId: string;
|
||||
section: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This type should eventually be replaced by something inside elastic-docs.
|
||||
* It's what will be passed to an elastic-docs supplied component to make
|
||||
* the API docs pretty.
|
||||
*/
|
||||
export interface ApiDeclaration {
|
||||
/**
|
||||
* Used for an anchor link to this Api. Can't use label as there can be two labels with the same
|
||||
* text within the Client section and the Server section.
|
||||
*/
|
||||
id?: string;
|
||||
|
||||
/**
|
||||
* The name of the api.
|
||||
*/
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* Should the list be expanded or collapsed initially?
|
||||
*/
|
||||
initialIsOpen?: boolean;
|
||||
|
||||
/**
|
||||
* The kind of type this API represents, e.g. string, number, Object, Interface, Class.
|
||||
*/
|
||||
type: TypeKind;
|
||||
|
||||
/**
|
||||
* Certain types have children. For instance classes have class members, functions will list
|
||||
* their parameters here, classes will list their class members here, and objects and interfaces
|
||||
* will list their properties.
|
||||
*/
|
||||
children?: ApiDeclaration[];
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
isRequired?: boolean;
|
||||
|
||||
/**
|
||||
* Api node comment.
|
||||
*/
|
||||
description?: TextWithLinks;
|
||||
|
||||
/**
|
||||
* If the type is a function, it's signature should be displayed. Currently this overlaps with type
|
||||
* sometimes, and will sometimes be left empty for large types (like classes and interfaces).
|
||||
*/
|
||||
signature?: TextWithLinks;
|
||||
|
||||
/**
|
||||
* Relevant for functions with @returns comments.
|
||||
*/
|
||||
returnComment?: TextWithLinks;
|
||||
|
||||
/**
|
||||
* Will contain the tags on a comment, like `beta` or `deprecated`.
|
||||
* Won't include param or returns tags.
|
||||
*/
|
||||
tags?: string[];
|
||||
|
||||
/**
|
||||
* Every plugn that exposes functionality from their setup and start contract
|
||||
* should have a single exported type for each. These get pulled to the top because
|
||||
* they are accessed differently than other exported functionality and types.
|
||||
*/
|
||||
lifecycle?: Lifecycle;
|
||||
|
||||
/**
|
||||
* Used to create links to github to view the code for this API.
|
||||
*/
|
||||
source: SourceLink;
|
||||
}
|
||||
|
||||
export interface SourceLink {
|
||||
path: string;
|
||||
lineNumber: number;
|
||||
link: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Developers will need to know whether these APIs are available on the client, server, or both.
|
||||
*/
|
||||
export enum ApiScope {
|
||||
CLIENT = 'public',
|
||||
SERVER = 'server',
|
||||
COMMON = 'common',
|
||||
}
|
||||
|
||||
/**
|
||||
* Start and Setup interfaces are special - their functionality is not imported statically but
|
||||
* accessible via the dependent plugins start and setup functions.
|
||||
*/
|
||||
export enum Lifecycle {
|
||||
START = 'start',
|
||||
SETUP = 'setup',
|
||||
}
|
83
packages/kbn-docs-utils/src/api_docs/utils.test.ts
Normal file
83
packages/kbn-docs-utils/src/api_docs/utils.test.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { KibanaPlatformPlugin, ToolingLog } from '@kbn/dev-utils';
|
||||
import Path from 'path';
|
||||
import { Project } from 'ts-morph';
|
||||
import { findPlugins } from './find_plugins';
|
||||
import { getPluginApi } from './get_plugin_api';
|
||||
import { getKibanaPlatformPlugin } from './tests/kibana_platform_plugin_mock';
|
||||
import { PluginApi } from './types';
|
||||
import { getPluginForPath, getServiceForPath, removeBrokenLinks } from './utils';
|
||||
|
||||
const log = new ToolingLog({
|
||||
level: 'debug',
|
||||
writeTo: process.stdout,
|
||||
});
|
||||
|
||||
it('test getPluginForPath', () => {
|
||||
const plugins = findPlugins();
|
||||
const path = Path.resolve(__dirname, '../../../../src/plugins/embeddable/public/service/file.ts');
|
||||
expect(getPluginForPath(path, plugins)).toBeDefined();
|
||||
});
|
||||
|
||||
it('test getServiceForPath', () => {
|
||||
expect(getServiceForPath('src/plugins/embed/public/service/file.ts', 'src/plugins/embed')).toBe(
|
||||
'service'
|
||||
);
|
||||
expect(
|
||||
getServiceForPath('src/plugins/embed/public/service/subfolder/file.ts', 'src/plugins/embed')
|
||||
).toBe('service');
|
||||
expect(
|
||||
getServiceForPath('src/plugins/embed/public/file.ts', 'src/plugins/embed')
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
getServiceForPath('/src/plugins/embed/server/another_service/index', '/src/plugins/embed')
|
||||
).toBe('another_service');
|
||||
expect(getServiceForPath('src/plugins/embed/server/no_ending', 'src/plugins/embed')).toBe(
|
||||
undefined
|
||||
);
|
||||
expect(
|
||||
getServiceForPath('src/plugins/embed/server/routes/public/foo/index.ts', 'src/plugins/embed')
|
||||
).toBe('routes');
|
||||
expect(getServiceForPath('src/plugins/embed/server/f.ts', 'src/plugins/embed')).toBeUndefined();
|
||||
|
||||
expect(
|
||||
getServiceForPath(
|
||||
'/var/lib/jenkins/workspace/elastic+kibana+pipeline-pull-request/kibana/packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/foo/index',
|
||||
'/var/lib/jenkins/workspace/elastic+kibana+pipeline-pull-request/kibana/packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a'
|
||||
)
|
||||
).toBe('foo');
|
||||
});
|
||||
|
||||
it('test removeBrokenLinks', () => {
|
||||
const tsConfigFilePath = Path.resolve(__dirname, 'tests/__fixtures__/src/tsconfig.json');
|
||||
const project = new Project({
|
||||
tsConfigFilePath,
|
||||
});
|
||||
|
||||
expect(project.getSourceFiles().length).toBeGreaterThan(0);
|
||||
|
||||
const pluginA = getKibanaPlatformPlugin('pluginA');
|
||||
pluginA.manifest.serviceFolders = ['foo'];
|
||||
const plugins: KibanaPlatformPlugin[] = [pluginA];
|
||||
|
||||
const pluginApiMap: { [key: string]: PluginApi } = {};
|
||||
plugins.map((plugin) => {
|
||||
pluginApiMap[plugin.manifest.id] = getPluginApi(project, plugin, plugins, log);
|
||||
});
|
||||
|
||||
const missingApiItems: { [key: string]: string[] } = {};
|
||||
|
||||
plugins.forEach((plugin) => {
|
||||
const id = plugin.manifest.id;
|
||||
const pluginApi = pluginApiMap[id];
|
||||
removeBrokenLinks(pluginApi, missingApiItems, pluginApiMap);
|
||||
});
|
||||
expect(missingApiItems.pluginA.indexOf('public.ImNotExportedFromIndex')).toBeGreaterThan(-1);
|
||||
});
|
208
packages/kbn-docs-utils/src/api_docs/utils.ts
Normal file
208
packages/kbn-docs-utils/src/api_docs/utils.ts
Normal file
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { KibanaPlatformPlugin, ToolingLog } from '@kbn/dev-utils';
|
||||
import {
|
||||
AnchorLink,
|
||||
ApiDeclaration,
|
||||
ScopeApi,
|
||||
TypeKind,
|
||||
Lifecycle,
|
||||
PluginApi,
|
||||
ApiScope,
|
||||
} from './types';
|
||||
|
||||
function capitalize(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
export const camelToSnake = (str: string): string => str.replace(/([A-Z])/g, '_$1').toLowerCase();
|
||||
|
||||
export const snakeToCamel = (str: string): string =>
|
||||
str.replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', ''));
|
||||
|
||||
/**
|
||||
* Returns the plugin that the file belongs to.
|
||||
* @param path An absolute file path that can point to a file nested inside a plugin
|
||||
* @param plugins A list of plugins to search through.
|
||||
*/
|
||||
export function getPluginForPath(
|
||||
path: string,
|
||||
plugins: KibanaPlatformPlugin[]
|
||||
): KibanaPlatformPlugin | undefined {
|
||||
return plugins.find((plugin) => path.startsWith(plugin.directory));
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups ApiDeclarations by typescript kind - classes, functions, enums, etc, so they
|
||||
* can be displayed separately in the mdx files.
|
||||
*/
|
||||
export function groupPluginApi(declarations: ApiDeclaration[]): ScopeApi {
|
||||
const scope = createEmptyScope();
|
||||
|
||||
declarations.forEach((declaration) => {
|
||||
addApiDeclarationToScope(declaration, scope);
|
||||
});
|
||||
|
||||
return scope;
|
||||
}
|
||||
|
||||
function escapeRegExp(regexp: string) {
|
||||
return regexp.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
}
|
||||
|
||||
/**
|
||||
* If the file is at the top level, returns undefined, otherwise returns the
|
||||
* name of the first nested folder in the plugin. For example a path of
|
||||
* 'src/plugins/data/public/search_services/file.ts' would return 'search_service' while
|
||||
* 'src/plugin/data/server/file.ts' would return undefined.
|
||||
* @param path
|
||||
*/
|
||||
export function getServiceForPath(path: string, pluginDirectory: string): string | undefined {
|
||||
const dir = escapeRegExp(pluginDirectory);
|
||||
const publicMatchGroups = path.match(`${dir}\/public\/([^\/]*)\/`);
|
||||
const serverMatchGroups = path.match(`${dir}\/server\/([^\/]*)\/`);
|
||||
const commonMatchGroups = path.match(`${dir}\/common\/([^\/]*)\/`);
|
||||
|
||||
if (publicMatchGroups && publicMatchGroups.length > 1) {
|
||||
return publicMatchGroups[1];
|
||||
} else if (serverMatchGroups && serverMatchGroups.length > 1) {
|
||||
return serverMatchGroups[1];
|
||||
} else if (commonMatchGroups && commonMatchGroups.length > 1) {
|
||||
return commonMatchGroups[1];
|
||||
}
|
||||
}
|
||||
|
||||
export function getPluginApiDocId(
|
||||
id: string,
|
||||
log: ToolingLog,
|
||||
serviceInfo?: {
|
||||
serviceFolders: readonly string[];
|
||||
apiPath: string;
|
||||
directory: string;
|
||||
}
|
||||
) {
|
||||
let service = '';
|
||||
const cleanName = id.replace('.', '_');
|
||||
if (serviceInfo) {
|
||||
const serviceName = getServiceForPath(serviceInfo.apiPath, serviceInfo.directory);
|
||||
log.debug(
|
||||
`Service for path ${serviceInfo.apiPath} and ${serviceInfo.directory} is ${serviceName}`
|
||||
);
|
||||
const serviceFolder = serviceInfo.serviceFolders?.find((f) => f === serviceName);
|
||||
|
||||
if (serviceFolder) {
|
||||
service = snakeToCamel(serviceFolder);
|
||||
}
|
||||
}
|
||||
|
||||
return `kib${capitalize(snakeToCamel(cleanName)) + capitalize(service)}PluginApi`;
|
||||
}
|
||||
|
||||
export function getApiSectionId(link: AnchorLink) {
|
||||
const id = `def-${link.scope}.${link.apiName}`.replace(' ', '-');
|
||||
return id;
|
||||
}
|
||||
|
||||
export function countScopeApi(api: ScopeApi): number {
|
||||
return (
|
||||
(api.setup ? 1 : 0) +
|
||||
(api.start ? 1 : 0) +
|
||||
api.classes.length +
|
||||
api.interfaces.length +
|
||||
api.functions.length +
|
||||
api.objects.length +
|
||||
api.enums.length +
|
||||
api.misc.length
|
||||
);
|
||||
}
|
||||
|
||||
export function createEmptyScope(): ScopeApi {
|
||||
return {
|
||||
classes: [],
|
||||
functions: [],
|
||||
interfaces: [],
|
||||
enums: [],
|
||||
misc: [],
|
||||
objects: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the ApiDeclaration and puts it in the appropriate section of the ScopeApi based
|
||||
* on its TypeKind.
|
||||
*/
|
||||
export function addApiDeclarationToScope(declaration: ApiDeclaration, scope: ScopeApi): void {
|
||||
if (declaration.lifecycle === Lifecycle.SETUP) {
|
||||
scope.setup = declaration;
|
||||
} else if (declaration.lifecycle === Lifecycle.START) {
|
||||
scope.start = declaration;
|
||||
} else {
|
||||
switch (declaration.type) {
|
||||
case TypeKind.ClassKind:
|
||||
scope.classes.push(declaration);
|
||||
break;
|
||||
case TypeKind.InterfaceKind:
|
||||
scope.interfaces.push(declaration);
|
||||
break;
|
||||
case TypeKind.EnumKind:
|
||||
scope.enums.push(declaration);
|
||||
break;
|
||||
case TypeKind.FunctionKind:
|
||||
scope.functions.push(declaration);
|
||||
break;
|
||||
case TypeKind.ObjectKind:
|
||||
scope.objects.push(declaration);
|
||||
break;
|
||||
default:
|
||||
scope.misc.push(declaration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function removeBrokenLinks(
|
||||
pluginApi: PluginApi,
|
||||
missingApiItems: { [key: string]: string[] },
|
||||
pluginApiMap: { [key: string]: PluginApi }
|
||||
) {
|
||||
(['client', 'common', 'server'] as Array<'client' | 'server' | 'common'>).forEach((scope) => {
|
||||
pluginApi[scope].forEach((api) => {
|
||||
if (api.signature) {
|
||||
api.signature = api.signature.map((sig) => {
|
||||
if (typeof sig !== 'string') {
|
||||
if (apiItemExists(sig.text, sig.scope, pluginApiMap[sig.pluginId]) === false) {
|
||||
if (missingApiItems[sig.pluginId] === undefined) {
|
||||
missingApiItems[sig.pluginId] = [];
|
||||
}
|
||||
missingApiItems[sig.pluginId].push(`${sig.scope}.${sig.text}`);
|
||||
return sig.text;
|
||||
}
|
||||
}
|
||||
return sig;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function apiItemExists(name: string, scope: ApiScope, pluginApi: PluginApi): boolean {
|
||||
return (
|
||||
pluginApi[scopeAccessor(scope)].findIndex((dec: ApiDeclaration) => dec.label === name) >= 0
|
||||
);
|
||||
}
|
||||
|
||||
function scopeAccessor(scope: ApiScope): 'server' | 'common' | 'client' {
|
||||
switch (scope) {
|
||||
case ApiScope.CLIENT:
|
||||
return 'client';
|
||||
case ApiScope.SERVER:
|
||||
return 'server';
|
||||
default:
|
||||
return 'common';
|
||||
}
|
||||
}
|
10
packages/kbn-docs-utils/src/index.ts
Normal file
10
packages/kbn-docs-utils/src/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './release_notes';
|
||||
export * from './api_docs';
|
|
@ -8,5 +8,8 @@
|
|||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"**/__fixtures__/**/*"
|
||||
]
|
||||
}
|
1215
packages/kbn-pm/dist/index.js
vendored
1215
packages/kbn-pm/dist/index.js
vendored
File diff suppressed because it is too large
Load diff
10
scripts/build_api_docs.js
Normal file
10
scripts/build_api_docs.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
require('../src/setup_node_env');
|
||||
require('@kbn/docs-utils').runBuildApiDocsCli();
|
|
@ -7,4 +7,4 @@
|
|||
*/
|
||||
|
||||
require('../src/setup_node_env/no_transpilation');
|
||||
require('@kbn/release-notes').runReleaseNotesCli();
|
||||
require('@kbn/docs-utils').runReleaseNotesCli();
|
||||
|
|
7
src/core/kibana.json
Normal file
7
src/core/kibana.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"id": "core",
|
||||
"summary": "The core plugin has core functionality",
|
||||
"version": "kibana",
|
||||
"serviceFolders": ["http", "saved_objects", "chrome", "application"]
|
||||
}
|
||||
|
|
@ -47,6 +47,7 @@ const KNOWN_MANIFEST_FIELDS = (() => {
|
|||
server: true,
|
||||
extraPublicDirs: true,
|
||||
requiredBundles: true,
|
||||
serviceFolders: true,
|
||||
};
|
||||
|
||||
return new Set(Object.keys(manifestFields));
|
||||
|
|
|
@ -169,6 +169,12 @@ export interface PluginManifest {
|
|||
* @deprecated
|
||||
*/
|
||||
readonly extraPublicDirs?: string[];
|
||||
|
||||
/**
|
||||
* Only used for the automatically generated API documentation. Specifying service
|
||||
* folders will cause your plugin API reference to be broken up into sub sections.
|
||||
*/
|
||||
readonly serviceFolders?: readonly string[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1883,6 +1883,7 @@ export interface PluginManifest {
|
|||
readonly requiredBundles: readonly string[];
|
||||
readonly requiredPlugins: readonly PluginName[];
|
||||
readonly server: boolean;
|
||||
readonly serviceFolders?: readonly string[];
|
||||
readonly ui: boolean;
|
||||
readonly version: string;
|
||||
}
|
||||
|
@ -3198,9 +3199,9 @@ export const validBodyOutput: readonly ["data", "stream"];
|
|||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/core/server/http/router/response.ts:297:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:280:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:280:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:283:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:388:5 - (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "create"
|
||||
// src/core/server/plugins/types.ts:286:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:286:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:289:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:394:5 - (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "create"
|
||||
|
||||
```
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"share",
|
||||
"inspector"
|
||||
],
|
||||
"serviceFolders": ["search", "index_patterns", "query", "autocomplete", "ui", "field_formats"],
|
||||
"optionalPlugins": ["usageCollection"],
|
||||
"extraPublicDirs": ["common"],
|
||||
"requiredBundles": [
|
||||
|
|
55
yarn.lock
55
yarn.lock
|
@ -2107,6 +2107,14 @@
|
|||
enabled "2.0.x"
|
||||
kuler "^2.0.0"
|
||||
|
||||
"@dsherret/to-absolute-glob@^2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@dsherret/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1f6475dc8bd974cea07a2daf3864b317b1dd332c"
|
||||
integrity sha1-H2R13IvZdM6gei2vOGSzF7HdMyw=
|
||||
dependencies:
|
||||
is-absolute "^1.0.0"
|
||||
is-negated-glob "^1.0.0"
|
||||
|
||||
"@elastic/apm-rum-core@^5.7.0":
|
||||
version "5.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@elastic/apm-rum-core/-/apm-rum-core-5.7.0.tgz#2213987285324781e2ebeca607f3a71245da5a84"
|
||||
|
@ -3428,6 +3436,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/docs-utils@link:packages/kbn-docs-utils":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/es-archiver@link:packages/kbn-es-archiver":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
@ -3484,10 +3496,6 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/release-notes@link:packages/kbn-release-notes":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/std@link:packages/kbn-std":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
@ -5134,6 +5142,18 @@
|
|||
dependencies:
|
||||
"@babel/runtime" "^7.10.2"
|
||||
|
||||
"@ts-morph/common@~0.7.0":
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.7.3.tgz#380020c278e4aa6cecedf362a1157591d1003267"
|
||||
integrity sha512-M6Tcu0EZDLL8Ht7WAYz7yJfDZ9eArhqR8XZ9Mk3q8jwU6MKFAttrw3JtW4JhneqTz7pZMv4XaimEdXI0E4K4rg==
|
||||
dependencies:
|
||||
"@dsherret/to-absolute-glob" "^2.0.2"
|
||||
fast-glob "^3.2.4"
|
||||
is-negated-glob "^1.0.0"
|
||||
mkdirp "^1.0.4"
|
||||
multimatch "^5.0.0"
|
||||
typescript "~4.1.2"
|
||||
|
||||
"@turf/along@6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@turf/along/-/along-6.0.1.tgz#595cecdc48fc7fcfa83c940a8e3eb24d4c2e04d4"
|
||||
|
@ -10561,6 +10581,11 @@ coa@^2.0.2:
|
|||
chalk "^2.4.1"
|
||||
q "^1.1.2"
|
||||
|
||||
code-block-writer@^10.1.1:
|
||||
version "10.1.1"
|
||||
resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-10.1.1.tgz#ad5684ed4bfb2b0783c8b131281ae84ee640a42f"
|
||||
integrity sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==
|
||||
|
||||
code-point-at@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||
|
@ -21176,6 +21201,17 @@ multimatch@^4.0.0:
|
|||
arrify "^2.0.1"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
multimatch@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6"
|
||||
integrity sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==
|
||||
dependencies:
|
||||
"@types/minimatch" "^3.0.3"
|
||||
array-differ "^3.0.0"
|
||||
array-union "^2.1.0"
|
||||
arrify "^2.0.1"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
multiparty@^4.1.2:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/multiparty/-/multiparty-4.2.1.tgz#d9b6c46d8b8deab1ee70c734b0af771dd46e0b13"
|
||||
|
@ -28444,6 +28480,15 @@ ts-log@2.1.4:
|
|||
resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.1.4.tgz#063c5ad1cbab5d49d258d18015963489fb6fb59a"
|
||||
integrity sha512-P1EJSoyV+N3bR/IWFeAqXzKPZwHpnLY6j7j58mAvewHRipo+BQM2Y1f9Y9BjEQznKwgqqZm7H8iuixmssU7tYQ==
|
||||
|
||||
ts-morph@^9.1.0:
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-9.1.0.tgz#10d2088387c71f3c674f82492a3cec1e3538f0dd"
|
||||
integrity sha512-sei4u651MBenr27sD6qLDXN3gZ4thiX71E3qV7SuVtDas0uvK2LtgZkIYUf9DKm/fLJ6AB/+yhRJ1vpEBJgy7Q==
|
||||
dependencies:
|
||||
"@dsherret/to-absolute-glob" "^2.0.2"
|
||||
"@ts-morph/common" "~0.7.0"
|
||||
code-block-writer "^10.1.1"
|
||||
|
||||
ts-pnp@^1.1.6:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
|
||||
|
@ -28648,7 +28693,7 @@ typescript-tuple@^2.2.1:
|
|||
dependencies:
|
||||
typescript-compare "^0.0.2"
|
||||
|
||||
typescript@4.1.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.5.3, typescript@~3.7.2:
|
||||
typescript@4.1.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.5.3, typescript@~3.7.2, typescript@~4.1.2:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"
|
||||
integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue