test(bazel): Build and test ts-api-guardian locally (#22544)
Also use it to test the public API for core and common Once we have an ng_package for every package, we can remove the npm dependency on ts-api-guardian and the gulp-based public api check. PR Close #22544
This commit is contained in:
parent
25faf808a5
commit
4f60968704
@ -2,8 +2,16 @@ package(default_visibility = ["//visibility:public"])
|
|||||||
|
|
||||||
exports_files([
|
exports_files([
|
||||||
"tsconfig.json",
|
"tsconfig.json",
|
||||||
|
"LICENSE",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
# Developers should always run `bazel run :install`
|
||||||
|
# This ensures that package.json in subdirectories get installed as well.
|
||||||
|
alias(
|
||||||
|
name = "install",
|
||||||
|
actual = "@yarn//:yarn",
|
||||||
|
)
|
||||||
|
|
||||||
# This rule belongs in node_modules/BUILD
|
# This rule belongs in node_modules/BUILD
|
||||||
# It's here as a workaround for
|
# It's here as a workaround for
|
||||||
# https://github.com/bazelbuild/bazel/issues/374#issuecomment-296217940
|
# https://github.com/bazelbuild/bazel/issues/374#issuecomment-296217940
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
workspace(name = "angular")
|
workspace(name = "angular")
|
||||||
|
|
||||||
# Using a pre-release snapshot to pick up a commit that makes all nodejs_binary
|
|
||||||
# programs produce source-mapped stack traces and uglify sourcemaps.
|
|
||||||
RULES_NODEJS_VERSION = "f3fc23b7e1f32984a3e5d0c7eabe3baa127fb32a"
|
|
||||||
|
|
||||||
http_archive(
|
http_archive(
|
||||||
name = "build_bazel_rules_nodejs",
|
name = "build_bazel_rules_nodejs",
|
||||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.5.0.zip",
|
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.5.0.zip",
|
||||||
@ -14,7 +10,10 @@ http_archive(
|
|||||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories")
|
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories")
|
||||||
|
|
||||||
check_bazel_version("0.9.0")
|
check_bazel_version("0.9.0")
|
||||||
node_repositories(package_json = ["//:package.json"])
|
node_repositories(package_json = [
|
||||||
|
"//:package.json",
|
||||||
|
"//tools/ts-api-guardian:package.json",
|
||||||
|
])
|
||||||
|
|
||||||
http_archive(
|
http_archive(
|
||||||
name = "build_bazel_rules_typescript",
|
name = "build_bazel_rules_typescript",
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
exports_files(["tsconfig.json"])
|
||||||
|
|
||||||
# Executes the workspace_status_command and provides the result.
|
# Executes the workspace_status_command and provides the result.
|
||||||
# See the section on stamping in docs/BAZEL.md
|
# See the section on stamping in docs/BAZEL.md
|
||||||
genrule(
|
genrule(
|
||||||
|
@ -26,6 +26,7 @@ const srcsToFmt = [
|
|||||||
`!${I18N_FOLDER}/currencies.ts`,
|
`!${I18N_FOLDER}/currencies.ts`,
|
||||||
`!${I18N_FOLDER}/locale_en.ts`,
|
`!${I18N_FOLDER}/locale_en.ts`,
|
||||||
'!tools/gulp-tasks/cldr/extract.js',
|
'!tools/gulp-tasks/cldr/extract.js',
|
||||||
|
'!tools/ts-api-guardian/test/fixtures/**',
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
26
tools/public_api_guard/BUILD.bazel
Normal file
26
tools/public_api_guard/BUILD.bazel
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
load("//tools/ts-api-guardian:index.bzl", "ts_api_guardian_test")
|
||||||
|
|
||||||
|
[
|
||||||
|
ts_api_guardian_test(
|
||||||
|
name = "%s_api" % i.replace("/", "_"),
|
||||||
|
actual = "packages/%s/npm_package/%s.d.ts" % (
|
||||||
|
i.split("/")[0],
|
||||||
|
"/".join(i.split("/")[1:]),
|
||||||
|
),
|
||||||
|
data = glob([
|
||||||
|
"%s/**/*.d.ts" % i.split("/")[0],
|
||||||
|
]) + [
|
||||||
|
"//packages/%s:npm_package" % i.split("/")[0],
|
||||||
|
],
|
||||||
|
golden = "tools/public_api_guard/%s.d.ts" % i,
|
||||||
|
)
|
||||||
|
for i in [
|
||||||
|
"core/core",
|
||||||
|
"core/testing",
|
||||||
|
"common/http/testing",
|
||||||
|
"common/common",
|
||||||
|
"common/http",
|
||||||
|
"common/testing",
|
||||||
|
# TODO(alexeagle): add remaining packages here once they have ng_package's
|
||||||
|
]
|
||||||
|
]
|
84
tools/ts-api-guardian/BUILD.bazel
Normal file
84
tools/ts-api-guardian/BUILD.bazel
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
load(
|
||||||
|
"@build_bazel_rules_nodejs//:defs.bzl",
|
||||||
|
"nodejs_binary",
|
||||||
|
"jasmine_node_test",
|
||||||
|
"npm_package",
|
||||||
|
"node_modules_filegroup",
|
||||||
|
)
|
||||||
|
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
|
||||||
|
|
||||||
|
exports_files(["bin/ts-api-guardian"])
|
||||||
|
|
||||||
|
node_modules_filegroup(
|
||||||
|
name = "compile_time_deps",
|
||||||
|
packages = [
|
||||||
|
"chalk",
|
||||||
|
"typescript",
|
||||||
|
"@types",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "lib",
|
||||||
|
srcs = glob(["lib/*.ts"]),
|
||||||
|
module_name = "ts-api-guardian",
|
||||||
|
node_modules = ":compile_time_deps",
|
||||||
|
tsconfig = "//tools:tsconfig.json",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
node_modules_filegroup(
|
||||||
|
name = "runtime_deps",
|
||||||
|
packages = [
|
||||||
|
"chai",
|
||||||
|
"chalk",
|
||||||
|
"jasmine",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy Angular's license to govern ts-api-guardian as well.
|
||||||
|
# We use a genrule to put it in this package, so it will be in the right root directory.
|
||||||
|
genrule(
|
||||||
|
name = "license",
|
||||||
|
srcs = ["//:LICENSE"],
|
||||||
|
outs = ["LICENSE"],
|
||||||
|
cmd = "cp $< $@",
|
||||||
|
)
|
||||||
|
|
||||||
|
npm_package(
|
||||||
|
name = "ts-api-guardian",
|
||||||
|
srcs = [
|
||||||
|
"README.md",
|
||||||
|
"bin/ts-api-guardian",
|
||||||
|
"package.json",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
":lib",
|
||||||
|
":license",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################3
|
||||||
|
# Tests for this package
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "test_lib",
|
||||||
|
testonly = True,
|
||||||
|
srcs = glob(["test/*.ts"]),
|
||||||
|
node_modules = ":compile_time_deps",
|
||||||
|
deps = [":lib"],
|
||||||
|
)
|
||||||
|
|
||||||
|
jasmine_node_test(
|
||||||
|
name = "tests",
|
||||||
|
srcs = [":test_lib"],
|
||||||
|
bootstrap = ["angular/tools/ts-api-guardian/test/bootstrap.js"],
|
||||||
|
data = glob([
|
||||||
|
"test/fixtures/*.ts",
|
||||||
|
"test/fixtures/*.patch",
|
||||||
|
]) + [
|
||||||
|
":ts-api-guardian",
|
||||||
|
],
|
||||||
|
node_modules = ":runtime_deps",
|
||||||
|
)
|
@ -1,3 +1,20 @@
|
|||||||
# Typescript API Guardian
|
# Typescript API Guardian
|
||||||
|
|
||||||
Keeps track of public API surface of a typescript library.
|
Keeps track of public API surface of a typescript library.
|
||||||
|
|
||||||
|
# For developers
|
||||||
|
|
||||||
|
Build and test this library:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ bazel run //:install
|
||||||
|
$ bazel test //tools/ts-api-guardian:all
|
||||||
|
```
|
||||||
|
|
||||||
|
Publish to NPM:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm whoami # should be logged in as angular
|
||||||
|
$ grep version tools/ts-api-guardian/package.json # advance as needed
|
||||||
|
$ bazel run //tools/ts-api-guardian:ts-api-guardian.publish
|
||||||
|
```
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
require('../build/lib/cli').startCli();
|
require('../lib/cli').startCli();
|
||||||
|
53
tools/ts-api-guardian/index.bzl
Normal file
53
tools/ts-api-guardian/index.bzl
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Copyright 2017 The Bazel Authors. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Runs ts_api_guardian
|
||||||
|
"""
|
||||||
|
load("@build_bazel_rules_nodejs//internal/node:node.bzl", "nodejs_test", "nodejs_binary")
|
||||||
|
|
||||||
|
COMMON_MODULE_IDENTIFIERS = ["angular", "jasmine", "protractor"]
|
||||||
|
|
||||||
|
def ts_api_guardian_test(name, golden, actual, data = [], **kwargs):
|
||||||
|
"""Runs ts_api_guardian
|
||||||
|
"""
|
||||||
|
data += [
|
||||||
|
"//tools/ts-api-guardian:lib",
|
||||||
|
"//tools/ts-api-guardian:bin/ts-api-guardian",
|
||||||
|
]
|
||||||
|
|
||||||
|
args = [
|
||||||
|
"--stripExportPattern", "^\(__\|ɵ\)",
|
||||||
|
"--onStabilityMissing", "error",
|
||||||
|
]
|
||||||
|
for i in COMMON_MODULE_IDENTIFIERS:
|
||||||
|
args += ["--allowModuleIdentifiers", i]
|
||||||
|
|
||||||
|
nodejs_test(
|
||||||
|
name = name,
|
||||||
|
data = data,
|
||||||
|
node_modules = "//tools/ts-api-guardian:runtime_deps",
|
||||||
|
entry_point = "angular/tools/ts-api-guardian/bin/ts-api-guardian",
|
||||||
|
templated_args = args + ["--verify", golden, actual],
|
||||||
|
testonly = 1,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
nodejs_binary(
|
||||||
|
name = name + ".accept",
|
||||||
|
data = data,
|
||||||
|
node_modules = "//tools/ts-api-guardian:runtime_deps",
|
||||||
|
entry_point = "angular/tools/ts-api-guardian/bin/ts-api-guardian",
|
||||||
|
templated_args = args + ["--out", golden, actual],
|
||||||
|
**kwargs
|
||||||
|
)
|
@ -1,6 +1,17 @@
|
|||||||
import chalk from 'chalk';
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
// tslint:disable:no-console
|
||||||
|
|
||||||
|
// TODO(alexeagle): why not import chalk from 'chalk'?
|
||||||
|
// Something to do with TS default export in UMD emit...
|
||||||
|
const chalk = require('chalk');
|
||||||
import * as minimist from 'minimist';
|
import * as minimist from 'minimist';
|
||||||
import {ParsedArgs} from 'minimist';
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import {SerializationOptions, generateGoldenFile, verifyAgainstGoldenFile} from './main';
|
import {SerializationOptions, generateGoldenFile, verifyAgainstGoldenFile} from './main';
|
||||||
@ -35,7 +46,7 @@ export function startCli() {
|
|||||||
onStabilityMissing: argv['onStabilityMissing'] || 'none'
|
onStabilityMissing: argv['onStabilityMissing'] || 'none'
|
||||||
};
|
};
|
||||||
|
|
||||||
if (['warn', 'error', 'none'].indexOf(options.onStabilityMissing) < 0) {
|
if (['warn', 'error', 'none'].indexOf(options.onStabilityMissing as string) < 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Argument for "--onStabilityMissing" option must be one of: "warn", "error", "none"');
|
'Argument for "--onStabilityMissing" option must be one of: "warn", "error", "none"');
|
||||||
}
|
}
|
||||||
@ -65,7 +76,8 @@ export function startCli() {
|
|||||||
lines.pop(); // Remove trailing newline
|
lines.pop(); // Remove trailing newline
|
||||||
}
|
}
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const chalkMap = {'-': chalk.red, '+': chalk.green, '@': chalk.cyan};
|
const chalkMap: {[key: string]:
|
||||||
|
any} = {'-': chalk.red, '+': chalk.green, '@': chalk.cyan};
|
||||||
const chalkFunc = chalkMap[line[0]] || chalk.reset;
|
const chalkFunc = chalkMap[line[0]] || chalk.reset;
|
||||||
console.log(chalkFunc(line));
|
console.log(chalkFunc(line));
|
||||||
}
|
}
|
||||||
@ -73,6 +85,11 @@ export function startCli() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasDiff) {
|
if (hasDiff) {
|
||||||
|
// Under bazel, give instructions how to use bazel run to accept the golden file.
|
||||||
|
if (!!process.env['BAZEL_TARGET']) {
|
||||||
|
console.error('\n\nAccept the new golden file:');
|
||||||
|
console.error(` bazel run ${process.env['BAZEL_TARGET']}.accept`);
|
||||||
|
}
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,9 +97,9 @@ export function startCli() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseArguments(input: string[]):
|
export function parseArguments(input: string[]):
|
||||||
{argv: ParsedArgs, mode: string, errors?: string[]} {
|
{argv: minimist.ParsedArgs, mode: string, errors: string[]} {
|
||||||
let help = false;
|
let help = false;
|
||||||
const errors = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
const argv = minimist(input, {
|
const argv = minimist(input, {
|
||||||
string: [
|
string: [
|
||||||
@ -95,7 +112,7 @@ export function parseArguments(input: string[]):
|
|||||||
'color', 'no-color'
|
'color', 'no-color'
|
||||||
],
|
],
|
||||||
alias: {'outFile': 'out', 'verifyFile': 'verify'},
|
alias: {'outFile': 'out', 'verifyFile': 'verify'},
|
||||||
unknown: option => {
|
unknown: (option: string) => {
|
||||||
if (option[0] === '-') {
|
if (option[0] === '-') {
|
||||||
errors.push(`Unknown option: ${option}`);
|
errors.push(`Unknown option: ${option}`);
|
||||||
help = true;
|
help = true;
|
||||||
@ -171,7 +188,7 @@ Options:
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function generateFileNamePairs(
|
export function generateFileNamePairs(
|
||||||
argv: ParsedArgs, mode: string): {entrypoint: string, goldenFile: string}[] {
|
argv: minimist.ParsedArgs, mode: string): {entrypoint: string, goldenFile: string}[] {
|
||||||
if (argv[mode]) {
|
if (argv[mode]) {
|
||||||
return [{entrypoint: argv._[0], goldenFile: argv[mode]}];
|
return [{entrypoint: argv._[0], goldenFile: argv[mode]}];
|
||||||
|
|
||||||
@ -179,7 +196,7 @@ export function generateFileNamePairs(
|
|||||||
let rootDir = argv['rootDir'] || '.';
|
let rootDir = argv['rootDir'] || '.';
|
||||||
const goldenDir = argv[mode + 'Dir'];
|
const goldenDir = argv[mode + 'Dir'];
|
||||||
|
|
||||||
return argv._.map(fileName => {
|
return argv._.map((fileName: string) => {
|
||||||
return {
|
return {
|
||||||
entrypoint: fileName,
|
entrypoint: fileName,
|
||||||
goldenFile: path.join(goldenDir, path.relative(rootDir, fileName))
|
goldenFile: path.join(goldenDir, path.relative(rootDir, fileName))
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
import {createPatch} from 'diff';
|
import {createPatch} from 'diff';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
@ -50,7 +58,7 @@ export function publicApiInternal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Diagnostic {
|
interface Diagnostic {
|
||||||
type: DiagnosticSeverity;
|
type?: DiagnosticSeverity;
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +95,8 @@ class ResolvedDeclarationEmitter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let decl: ts.Node = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0];
|
let decl: ts.Node|undefined =
|
||||||
|
symbol.valueDeclaration || symbol.declarations && symbol.declarations[0];
|
||||||
if (!decl) {
|
if (!decl) {
|
||||||
this.diagnostics.push({
|
this.diagnostics.push({
|
||||||
type: 'warn',
|
type: 'warn',
|
||||||
@ -115,7 +124,7 @@ class ResolvedDeclarationEmitter {
|
|||||||
const match = stabilityAnnotationPattern.exec(trivia);
|
const match = stabilityAnnotationPattern.exec(trivia);
|
||||||
if (match) {
|
if (match) {
|
||||||
output += `/** @${match[1]} */\n`;
|
output += `/** @${match[1]} */\n`;
|
||||||
} else if (['warn', 'error'].indexOf(this.options.onStabilityMissing) >= 0) {
|
} else if (['warn', 'error'].indexOf(this.options.onStabilityMissing as string) >= 0) {
|
||||||
this.diagnostics.push({
|
this.diagnostics.push({
|
||||||
type: this.options.onStabilityMissing,
|
type: this.options.onStabilityMissing,
|
||||||
message: createErrorMessage(
|
message: createErrorMessage(
|
||||||
@ -177,20 +186,23 @@ class ResolvedDeclarationEmitter {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstQualifier: ts.Identifier = getFirstQualifier(node);
|
const firstQualifier: ts.Identifier|null = getFirstQualifier(node);
|
||||||
|
|
||||||
if (firstQualifier) {
|
if (firstQualifier) {
|
||||||
let isAllowed = false;
|
let isAllowed = false;
|
||||||
|
|
||||||
// Try to resolve the qualifier.
|
// Try to resolve the qualifier.
|
||||||
const resolvedSymbol = this.typeChecker.getSymbolAtLocation(firstQualifier);
|
const resolvedSymbol = this.typeChecker.getSymbolAtLocation(firstQualifier);
|
||||||
if (resolvedSymbol && resolvedSymbol.declarations.length > 0) {
|
if (resolvedSymbol && resolvedSymbol.declarations && resolvedSymbol.declarations.length > 0) {
|
||||||
// If the qualifier can be resolved, and it's not a namespaced import, then it should be allowed.
|
// If the qualifier can be resolved, and it's not a namespaced import, then it should be
|
||||||
isAllowed = resolvedSymbol.declarations.every(decl => decl.kind !== ts.SyntaxKind.NamespaceImport);
|
// allowed.
|
||||||
|
isAllowed =
|
||||||
|
resolvedSymbol.declarations.every(decl => decl.kind !== ts.SyntaxKind.NamespaceImport);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it is not allowed otherwise, it's allowed if it's on the list of allowed identifiers.
|
// If it is not allowed otherwise, it's allowed if it's on the list of allowed identifiers.
|
||||||
isAllowed = isAllowed || !(!this.options.allowModuleIdentifiers ||
|
isAllowed = isAllowed ||
|
||||||
|
!(!this.options.allowModuleIdentifiers ||
|
||||||
this.options.allowModuleIdentifiers.indexOf(firstQualifier.text) < 0);
|
this.options.allowModuleIdentifiers.indexOf(firstQualifier.text) < 0);
|
||||||
if (!isAllowed) {
|
if (!isAllowed) {
|
||||||
this.diagnostics.push({
|
this.diagnostics.push({
|
||||||
@ -221,8 +233,7 @@ class ResolvedDeclarationEmitter {
|
|||||||
// Static after normal
|
// Static after normal
|
||||||
return compareFunction(
|
return compareFunction(
|
||||||
hasModifier(a, ts.SyntaxKind.StaticKeyword),
|
hasModifier(a, ts.SyntaxKind.StaticKeyword),
|
||||||
hasModifier(b, ts.SyntaxKind.StaticKeyword)
|
hasModifier(b, ts.SyntaxKind.StaticKeyword)) ||
|
||||||
) ||
|
|
||||||
// Our predefined order
|
// Our predefined order
|
||||||
compareFunction(
|
compareFunction(
|
||||||
memberDeclarationOrder[a.kind], memberDeclarationOrder[b.kind]) ||
|
memberDeclarationOrder[a.kind], memberDeclarationOrder[b.kind]) ||
|
||||||
@ -236,8 +247,7 @@ class ResolvedDeclarationEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = children
|
let output: string = children.filter(x => x.kind !== ts.SyntaxKind.JSDocComment)
|
||||||
.filter(x => x.kind !== ts.SyntaxKind.JSDocComment)
|
|
||||||
.map(n => this.emitNode(n))
|
.map(n => this.emitNode(n))
|
||||||
.join('');
|
.join('');
|
||||||
|
|
||||||
@ -273,7 +283,7 @@ function compareFunction<T>(a: T, b: T) {
|
|||||||
return a === b ? 0 : a > b ? 1 : -1;
|
return a === b ? 0 : a > b ? 1 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const memberDeclarationOrder = {
|
const memberDeclarationOrder: {[key: number]: number} = {
|
||||||
[ts.SyntaxKind.PropertySignature]: 0,
|
[ts.SyntaxKind.PropertySignature]: 0,
|
||||||
[ts.SyntaxKind.PropertyDeclaration]: 0,
|
[ts.SyntaxKind.PropertyDeclaration]: 0,
|
||||||
[ts.SyntaxKind.GetAccessor]: 0,
|
[ts.SyntaxKind.GetAccessor]: 0,
|
||||||
@ -295,7 +305,7 @@ function stripEmptyLines(text: string): string {
|
|||||||
/**
|
/**
|
||||||
* Returns the first qualifier if the input node is a dotted expression.
|
* Returns the first qualifier if the input node is a dotted expression.
|
||||||
*/
|
*/
|
||||||
function getFirstQualifier(node: ts.Node): ts.Identifier {
|
function getFirstQualifier(node: ts.Node): ts.Identifier|null {
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
case ts.SyntaxKind.PropertyAccessExpression: {
|
case ts.SyntaxKind.PropertyAccessExpression: {
|
||||||
// For expression position
|
// For expression position
|
||||||
@ -324,7 +334,7 @@ function createErrorMessage(node: ts.Node, message: string): string {
|
|||||||
const sourceFile = node.getSourceFile();
|
const sourceFile = node.getSourceFile();
|
||||||
let position;
|
let position;
|
||||||
if (sourceFile) {
|
if (sourceFile) {
|
||||||
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
const {line, character} = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
||||||
position = `${sourceFile.fileName}(${line + 1},${character + 1})`;
|
position = `${sourceFile.fileName}(${line + 1},${character + 1})`;
|
||||||
} else {
|
} else {
|
||||||
position = '<unknown>';
|
position = '<unknown>';
|
||||||
|
@ -33,14 +33,11 @@
|
|||||||
"gulp-typescript": "^4.0.1",
|
"gulp-typescript": "^4.0.1",
|
||||||
"gulp-util": "^3.0.8",
|
"gulp-util": "^3.0.8",
|
||||||
"merge2": "^1.2.1",
|
"merge2": "^1.2.1",
|
||||||
|
"jasmine": "^3.1.0",
|
||||||
"source-map": "^0.7.1",
|
"source-map": "^0.7.1",
|
||||||
"source-map-support": "^0.5.3",
|
"source-map-support": "^0.5.3",
|
||||||
"typescript": "~2.6.2"
|
"typescript": "~2.6.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
|
||||||
"prepublish": "gulp compile",
|
|
||||||
"test": "gulp test.unit"
|
|
||||||
},
|
|
||||||
"repository": {},
|
"repository": {},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"typescript"
|
"typescript"
|
||||||
@ -54,7 +51,7 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/angular/ts-api-guardian/issues"
|
"url": "https://github.com/angular/angular/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/angular/ts-api-guardian"
|
"homepage": "https://github.com/angular/angular/tools/ts-api-guardian"
|
||||||
}
|
}
|
||||||
|
12
tools/ts-api-guardian/test/bootstrap.ts
Normal file
12
tools/ts-api-guardian/test/bootstrap.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as path from 'path';
|
||||||
|
// Before running tests, change directories to our directory under the runfiles
|
||||||
|
// From runfiles we want to go to the angular/tools/ts-api-guardian subfolder.
|
||||||
|
process.chdir(path.join(process.env['TEST_SRCDIR'], 'angular', 'tools', 'ts-api-guardian'));
|
@ -1,13 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
import chai = require('chai');
|
import chai = require('chai');
|
||||||
import * as child_process from 'child_process';
|
import * as child_process from 'child_process';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import {assertFileEqual, unlinkRecursively} from './helpers';
|
import {assertFileEqual, unlinkRecursively} from './helpers';
|
||||||
|
|
||||||
const BINARY = path.resolve(__dirname, '../../bin/ts-api-guardian');
|
const BINARY = 'ts-api-guardian/bin/ts-api-guardian';
|
||||||
|
|
||||||
describe('cli: e2e test', () => {
|
describe('cli: e2e test', () => {
|
||||||
const outDir = path.resolve(__dirname, '../../build/tmp');
|
const outDir = path.join(process.env['TEST_TMPDIR'], 'tmp');
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
if (!fs.existsSync(outDir)) {
|
if (!fs.existsSync(outDir)) {
|
||||||
@ -29,8 +37,8 @@ describe('cli: e2e test', () => {
|
|||||||
|
|
||||||
it('should generate golden file with --out', () => {
|
it('should generate golden file with --out', () => {
|
||||||
const simpleFile = path.join(outDir, 'simple.d.ts');
|
const simpleFile = path.join(outDir, 'simple.d.ts');
|
||||||
const {status} = execute(['--out', simpleFile, 'test/fixtures/simple.d.ts']);
|
const {status, stderr} = execute(['--out', simpleFile, 'test/fixtures/simple.d.ts']);
|
||||||
chai.assert.equal(status, 0);
|
chai.assert.equal(status, 0, stderr);
|
||||||
assertFileEqual(simpleFile, 'test/fixtures/simple_expected.d.ts');
|
assertFileEqual(simpleFile, 'test/fixtures/simple_expected.d.ts');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -117,7 +125,7 @@ describe('cli: e2e test', () => {
|
|||||||
stderr,
|
stderr,
|
||||||
'test/fixtures/simple.d.ts(1,1): error: No stability annotation found for symbol "A"\n' +
|
'test/fixtures/simple.d.ts(1,1): error: No stability annotation found for symbol "A"\n' +
|
||||||
'test/fixtures/simple.d.ts(2,1): error: No stability annotation found for symbol "B"\n');
|
'test/fixtures/simple.d.ts(2,1): error: No stability annotation found for symbol "B"\n');
|
||||||
chai.assert.equal(status, 0);
|
chai.assert.equal(status, 0, stderr);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -126,8 +134,12 @@ function copyFile(sourceFile: string, targetFile: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function execute(args: string[]): {stdout: string, stderr: string, status: number} {
|
function execute(args: string[]): {stdout: string, stderr: string, status: number} {
|
||||||
const output = child_process.spawnSync(BINARY, args);
|
const output = child_process.spawnSync(process.execPath, [path.resolve(BINARY), ...args], {
|
||||||
chai.assert(!output.error, 'Child process failed or timed out');
|
env: {
|
||||||
|
'NODE_PATH': process.cwd(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
chai.assert(!output.error, 'Child process failed or timed out: ' + output.error);
|
||||||
chai.assert(!output.signal, `Child process killed by signal ${output.signal}`);
|
chai.assert(!output.signal, `Child process killed by signal ${output.signal}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
import chai = require('chai');
|
import chai = require('chai');
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import {parseArguments, generateFileNamePairs} from '../lib/cli';
|
import {parseArguments, generateFileNamePairs} from '../lib/cli';
|
||||||
|
7
tools/ts-api-guardian/test/fixtures/empty.ts
vendored
7
tools/ts-api-guardian/test/fixtures/empty.ts
vendored
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
@ -1,3 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
import * as chai from 'chai';
|
import * as chai from 'chai';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
import * as chai from 'chai';
|
import * as chai from 'chai';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
@ -5,7 +13,7 @@ import * as main from '../lib/main';
|
|||||||
import {assertFileEqual, unlinkRecursively} from './helpers';
|
import {assertFileEqual, unlinkRecursively} from './helpers';
|
||||||
|
|
||||||
describe('integration test: public api', () => {
|
describe('integration test: public api', () => {
|
||||||
let _warn = null;
|
let _warn: any = null;
|
||||||
let warnings: string[] = [];
|
let warnings: string[] = [];
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
_warn = console.warn;
|
_warn = console.warn;
|
||||||
@ -71,7 +79,7 @@ describe('integration test: public api', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('integration test: generateGoldenFile', () => {
|
describe('integration test: generateGoldenFile', () => {
|
||||||
const outDir = path.resolve(__dirname, '../../build/tmp');
|
const outDir = path.join(process.env['TEST_TMPDIR'], 'tmp');
|
||||||
const outFile = path.join(outDir, 'out.d.ts');
|
const outFile = path.join(outDir, 'out.d.ts');
|
||||||
const deepOutFile = path.join(outDir, 'a/b/c/out.d.ts');
|
const deepOutFile = path.join(outDir, 'a/b/c/out.d.ts');
|
||||||
|
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
import * as chai from 'chai';
|
import * as chai from 'chai';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import { publicApiInternal, SerializationOptions } from '../lib/serializer';
|
|
||||||
|
import {SerializationOptions, publicApiInternal} from '../lib/serializer';
|
||||||
|
|
||||||
const classesAndInterfaces = `
|
const classesAndInterfaces = `
|
||||||
export declare class A {
|
export declare class A {
|
||||||
@ -20,7 +29,7 @@ const classesAndInterfaces = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
describe('unit test', () => {
|
describe('unit test', () => {
|
||||||
let _warn = null;
|
let _warn: any = null;
|
||||||
let warnings: string[] = [];
|
let warnings: string[] = [];
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
_warn = console.warn;
|
_warn = console.warn;
|
||||||
@ -47,7 +56,7 @@ describe('unit test', () => {
|
|||||||
protected fb(): void;
|
protected fb(): void;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
check({ 'file.d.ts': input }, expected);
|
check({'file.d.ts': input}, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore private props', () => {
|
it('should ignore private props', () => {
|
||||||
@ -64,7 +73,7 @@ describe('unit test', () => {
|
|||||||
protected fb: any;
|
protected fb: any;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
check({ 'file.d.ts': input }, expected);
|
check({'file.d.ts': input}, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support imports without capturing imports', () => {
|
it('should support imports without capturing imports', () => {
|
||||||
@ -79,7 +88,7 @@ describe('unit test', () => {
|
|||||||
field: A;
|
field: A;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
check({ 'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input }, expected);
|
check({'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input}, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw on aliased reexports', () => {
|
it('should throw on aliased reexports', () => {
|
||||||
@ -87,7 +96,7 @@ describe('unit test', () => {
|
|||||||
export { A as Apple } from './classes_and_interfaces';
|
export { A as Apple } from './classes_and_interfaces';
|
||||||
`;
|
`;
|
||||||
checkThrows(
|
checkThrows(
|
||||||
{ 'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input },
|
{'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input},
|
||||||
'Symbol "A" was aliased as "Apple". Aliases are not supported.');
|
'Symbol "A" was aliased as "Apple". Aliases are not supported.');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -97,7 +106,7 @@ describe('unit test', () => {
|
|||||||
`;
|
`;
|
||||||
const expected = `
|
const expected = `
|
||||||
`;
|
`;
|
||||||
check({ 'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input }, expected);
|
check({'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input}, expected);
|
||||||
chai.assert.deepEqual(
|
chai.assert.deepEqual(
|
||||||
warnings, ['file.d.ts(1,1): error: No export declaration found for symbol "Foo"']);
|
warnings, ['file.d.ts(1,1): error: No export declaration found for symbol "Foo"']);
|
||||||
});
|
});
|
||||||
@ -134,7 +143,7 @@ describe('unit test', () => {
|
|||||||
|
|
||||||
export declare type E = string;
|
export declare type E = string;
|
||||||
`;
|
`;
|
||||||
check({ 'file.d.ts': input }, expected);
|
check({'file.d.ts': input}, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sort class members', () => {
|
it('should sort class members', () => {
|
||||||
@ -158,7 +167,7 @@ describe('unit test', () => {
|
|||||||
static foo(): void;
|
static foo(): void;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
check({ 'file.d.ts': input }, expected);
|
check({'file.d.ts': input}, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sort interface members', () => {
|
it('should sort interface members', () => {
|
||||||
@ -180,7 +189,7 @@ describe('unit test', () => {
|
|||||||
c(): void;
|
c(): void;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
check({ 'file.d.ts': input }, expected);
|
check({'file.d.ts': input}, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sort class members including readonly', () => {
|
it('should sort class members including readonly', () => {
|
||||||
@ -215,7 +224,7 @@ describe('unit test', () => {
|
|||||||
constructor(nativeNode: any, parent: DebugNode | null, _debugContext: any);
|
constructor(nativeNode: any, parent: DebugNode | null, _debugContext: any);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
check({ 'file.d.ts': input }, expected);
|
check({'file.d.ts': input}, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sort two call signatures', () => {
|
it('should sort two call signatures', () => {
|
||||||
@ -231,7 +240,7 @@ describe('unit test', () => {
|
|||||||
(b: number): void;
|
(b: number): void;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
check({ 'file.d.ts': input }, expected);
|
check({'file.d.ts': input}, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sort exports including re-exports', () => {
|
it('should sort exports including re-exports', () => {
|
||||||
@ -269,7 +278,7 @@ describe('unit test', () => {
|
|||||||
|
|
||||||
export declare type E = string;
|
export declare type E = string;
|
||||||
`;
|
`;
|
||||||
check({ 'submodule.d.ts': submodule, 'file.d.ts': input }, expected);
|
check({'submodule.d.ts': submodule, 'file.d.ts': input}, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove module comments', () => {
|
it('should remove module comments', () => {
|
||||||
@ -289,7 +298,7 @@ describe('unit test', () => {
|
|||||||
|
|
||||||
export declare function foo(): boolean;
|
export declare function foo(): boolean;
|
||||||
`;
|
`;
|
||||||
check({ 'file.d.ts': input }, expected);
|
check({'file.d.ts': input}, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove class and field comments', () => {
|
it('should remove class and field comments', () => {
|
||||||
@ -314,7 +323,7 @@ describe('unit test', () => {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
check({ 'file.d.ts': input }, expected);
|
check({'file.d.ts': input}, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip symbols matching specified pattern', () => {
|
it('should skip symbols matching specified pattern', () => {
|
||||||
@ -327,7 +336,7 @@ describe('unit test', () => {
|
|||||||
export class B {
|
export class B {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
check({ 'file.d.ts': input }, expected, { stripExportPattern: /^__.*/ });
|
check({'file.d.ts': input}, expected, {stripExportPattern: /^__.*/});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw on using non-whitelisted module imports in expression position', () => {
|
it('should throw on using non-whitelisted module imports in expression position', () => {
|
||||||
@ -337,7 +346,7 @@ describe('unit test', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
checkThrows(
|
checkThrows(
|
||||||
{ 'file.d.ts': input }, 'file.d.ts(2,32): error: Module identifier "foo" is not allowed. ' +
|
{'file.d.ts': input}, 'file.d.ts(2,32): error: Module identifier "foo" is not allowed. ' +
|
||||||
'Remove it from source or whitelist it via --allowModuleIdentifiers.');
|
'Remove it from source or whitelist it via --allowModuleIdentifiers.');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -347,7 +356,7 @@ describe('unit test', () => {
|
|||||||
export type A = foo.A;
|
export type A = foo.A;
|
||||||
`;
|
`;
|
||||||
checkThrows(
|
checkThrows(
|
||||||
{ 'file.d.ts': input }, 'file.d.ts(2,17): error: Module identifier "foo" is not allowed. ' +
|
{'file.d.ts': input}, 'file.d.ts(2,17): error: Module identifier "foo" is not allowed. ' +
|
||||||
'Remove it from source or whitelist it via --allowModuleIdentifiers.');
|
'Remove it from source or whitelist it via --allowModuleIdentifiers.');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -361,7 +370,7 @@ describe('unit test', () => {
|
|||||||
export declare class A extends foo.A {
|
export declare class A extends foo.A {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
check({ 'file.d.ts': input }, expected, { allowModuleIdentifiers: ['foo'] });
|
check({'file.d.ts': input}, expected, {allowModuleIdentifiers: ['foo']});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw if non-whitelisted module imports are not written', () => {
|
it('should not throw if non-whitelisted module imports are not written', () => {
|
||||||
@ -374,7 +383,7 @@ describe('unit test', () => {
|
|||||||
export declare class A {
|
export declare class A {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
check({ 'file.d.ts': input }, expected);
|
check({'file.d.ts': input}, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should keep stability annotations of exports in docstrings', () => {
|
it('should keep stability annotations of exports in docstrings', () => {
|
||||||
@ -404,7 +413,7 @@ describe('unit test', () => {
|
|||||||
/** @stable */
|
/** @stable */
|
||||||
export declare var c: number;
|
export declare var c: number;
|
||||||
`;
|
`;
|
||||||
check({ 'file.d.ts': input }, expected);
|
check({'file.d.ts': input}, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should keep stability annotations of fields in docstrings', () => {
|
it('should keep stability annotations of fields in docstrings', () => {
|
||||||
@ -431,7 +440,7 @@ describe('unit test', () => {
|
|||||||
/** @deprecated */ foo(): void;
|
/** @deprecated */ foo(): void;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
check({ 'file.d.ts': input }, expected);
|
check({'file.d.ts': input}, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should warn on onStabilityMissing: warn', () => {
|
it('should warn on onStabilityMissing: warn', () => {
|
||||||
@ -445,20 +454,20 @@ describe('unit test', () => {
|
|||||||
constructor();
|
constructor();
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
check({ 'file.d.ts': input }, expected, { onStabilityMissing: 'warn' });
|
check({'file.d.ts': input}, expected, {onStabilityMissing: 'warn'});
|
||||||
chai.assert.deepEqual(
|
chai.assert.deepEqual(
|
||||||
warnings, ['file.d.ts(1,1): error: No stability annotation found for symbol "A"']);
|
warnings, ['file.d.ts(1,1): error: No stability annotation found for symbol "A"']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function getMockHost(files: { [name: string]: string }): ts.CompilerHost {
|
function getMockHost(files: {[name: string]: string}): ts.CompilerHost {
|
||||||
return {
|
return {
|
||||||
getSourceFile: (sourceName, languageVersion) => {
|
getSourceFile: (sourceName, languageVersion) => {
|
||||||
if (!files[sourceName]) return undefined;
|
if (!files[sourceName]) return undefined;
|
||||||
return ts.createSourceFile(
|
return ts.createSourceFile(
|
||||||
sourceName, stripExtraIndentation(files[sourceName]), languageVersion, true);
|
sourceName, stripExtraIndentation(files[sourceName]), languageVersion, true);
|
||||||
},
|
},
|
||||||
writeFile: (name, text, writeByteOrderMark) => { },
|
writeFile: (name, text, writeByteOrderMark) => {},
|
||||||
fileExists: (filename) => !!files[filename],
|
fileExists: (filename) => !!files[filename],
|
||||||
readFile: (filename) => stripExtraIndentation(files[filename]),
|
readFile: (filename) => stripExtraIndentation(files[filename]),
|
||||||
getDefaultLibFileName: () => 'lib.ts',
|
getDefaultLibFileName: () => 'lib.ts',
|
||||||
@ -471,12 +480,12 @@ function getMockHost(files: { [name: string]: string }): ts.CompilerHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function check(
|
function check(
|
||||||
files: { [name: string]: string }, expected: string, options: SerializationOptions = {}) {
|
files: {[name: string]: string}, expected: string, options: SerializationOptions = {}) {
|
||||||
const actual = publicApiInternal(getMockHost(files), 'file.d.ts', {}, options);
|
const actual = publicApiInternal(getMockHost(files), 'file.d.ts', {}, options);
|
||||||
chai.assert.equal(actual.trim(), stripExtraIndentation(expected).trim());
|
chai.assert.equal(actual.trim(), stripExtraIndentation(expected).trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkThrows(files: { [name: string]: string }, error: string) {
|
function checkThrows(files: {[name: string]: string}, error: string) {
|
||||||
chai.assert.throws(() => { publicApiInternal(getMockHost(files), 'file.d.ts', {}); }, error);
|
chai.assert.throws(() => { publicApiInternal(getMockHost(files), 'file.d.ts', {}); }, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -485,7 +494,7 @@ function stripExtraIndentation(text: string) {
|
|||||||
// Ignore first and last new line
|
// Ignore first and last new line
|
||||||
lines = lines.slice(1, lines.length - 1);
|
lines = lines.slice(1, lines.length - 1);
|
||||||
const commonIndent = lines.reduce((min, line) => {
|
const commonIndent = lines.reduce((min, line) => {
|
||||||
const indent = /^( *)/.exec(line)[1].length;
|
const indent = /^( *)/.exec(line) ![1].length;
|
||||||
// Ignore empty line
|
// Ignore empty line
|
||||||
return line.length ? Math.min(min, indent) : min;
|
return line.length ? Math.min(min, indent) : min;
|
||||||
}, text.length);
|
}, text.length);
|
||||||
|
2336
tools/ts-api-guardian/yarn.lock
Normal file
2336
tools/ts-api-guardian/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,7 @@
|
|||||||
"exclude": [
|
"exclude": [
|
||||||
"testing",
|
"testing",
|
||||||
"node_modules",
|
"node_modules",
|
||||||
|
"ts-api-guardian",
|
||||||
"typings-test",
|
"typings-test",
|
||||||
"public_api_guard",
|
"public_api_guard",
|
||||||
"docs"
|
"docs"
|
||||||
|
20
yarn.lock
20
yarn.lock
@ -288,9 +288,9 @@ ansi-styles@^2.2.1:
|
|||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
||||||
|
|
||||||
ansi-styles@^3.2.0:
|
ansi-styles@^3.2.1:
|
||||||
version "3.2.0"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
|
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||||
dependencies:
|
dependencies:
|
||||||
color-convert "^1.9.0"
|
color-convert "^1.9.0"
|
||||||
|
|
||||||
@ -1008,12 +1008,12 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
|
|||||||
supports-color "^2.0.0"
|
supports-color "^2.0.0"
|
||||||
|
|
||||||
chalk@^2.3.1:
|
chalk@^2.3.1:
|
||||||
version "2.3.1"
|
version "2.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796"
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65"
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-styles "^3.2.0"
|
ansi-styles "^3.2.1"
|
||||||
escape-string-regexp "^1.0.5"
|
escape-string-regexp "^1.0.5"
|
||||||
supports-color "^5.2.0"
|
supports-color "^5.3.0"
|
||||||
|
|
||||||
char-spinner@^1.0.1:
|
char-spinner@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
@ -6871,9 +6871,9 @@ supports-color@^3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^1.0.0"
|
has-flag "^1.0.0"
|
||||||
|
|
||||||
supports-color@^5.2.0:
|
supports-color@^5.3.0:
|
||||||
version "5.2.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.3.0.tgz#5b24ac15db80fa927cf5227a4a33fd3c4c7676c0"
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^3.0.0"
|
has-flag "^3.0.0"
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user