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:
Alex Eagle 2018-03-01 10:41:35 -08:00
parent 25faf808a5
commit 4f60968704
23 changed files with 2731 additions and 108 deletions

View File

@ -2,8 +2,16 @@ package(default_visibility = ["//visibility:public"])
exports_files([
"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
# It's here as a workaround for
# https://github.com/bazelbuild/bazel/issues/374#issuecomment-296217940

View File

@ -1,9 +1,5 @@
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(
name = "build_bazel_rules_nodejs",
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")
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(
name = "build_bazel_rules_typescript",

View File

@ -1,3 +1,5 @@
exports_files(["tsconfig.json"])
# Executes the workspace_status_command and provides the result.
# See the section on stamping in docs/BAZEL.md
genrule(

View File

@ -26,6 +26,7 @@ const srcsToFmt = [
`!${I18N_FOLDER}/currencies.ts`,
`!${I18N_FOLDER}/locale_en.ts`,
'!tools/gulp-tasks/cldr/extract.js',
'!tools/ts-api-guardian/test/fixtures/**',
];
module.exports = {

View 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
]
]

View 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",
)

View File

@ -1,3 +1,20 @@
# Typescript API Guardian
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
```

View File

@ -1,3 +1,3 @@
#!/usr/bin/env node
require('../build/lib/cli').startCli();
require('../lib/cli').startCli();

View 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
)

View File

@ -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 {ParsedArgs} from 'minimist';
import * as path from 'path';
import {SerializationOptions, generateGoldenFile, verifyAgainstGoldenFile} from './main';
@ -35,7 +46,7 @@ export function startCli() {
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(
'Argument for "--onStabilityMissing" option must be one of: "warn", "error", "none"');
}
@ -65,7 +76,8 @@ export function startCli() {
lines.pop(); // Remove trailing newline
}
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;
console.log(chalkFunc(line));
}
@ -73,6 +85,11 @@ export function startCli() {
}
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);
}
}
@ -80,9 +97,9 @@ export function startCli() {
}
export function parseArguments(input: string[]):
{argv: ParsedArgs, mode: string, errors?: string[]} {
{argv: minimist.ParsedArgs, mode: string, errors: string[]} {
let help = false;
const errors = [];
const errors: string[] = [];
const argv = minimist(input, {
string: [
@ -95,7 +112,7 @@ export function parseArguments(input: string[]):
'color', 'no-color'
],
alias: {'outFile': 'out', 'verifyFile': 'verify'},
unknown: option => {
unknown: (option: string) => {
if (option[0] === '-') {
errors.push(`Unknown option: ${option}`);
help = true;
@ -171,7 +188,7 @@ Options:
}
export function generateFileNamePairs(
argv: ParsedArgs, mode: string): {entrypoint: string, goldenFile: string}[] {
argv: minimist.ParsedArgs, mode: string): {entrypoint: string, goldenFile: string}[] {
if (argv[mode]) {
return [{entrypoint: argv._[0], goldenFile: argv[mode]}];
@ -179,7 +196,7 @@ export function generateFileNamePairs(
let rootDir = argv['rootDir'] || '.';
const goldenDir = argv[mode + 'Dir'];
return argv._.map(fileName => {
return argv._.map((fileName: string) => {
return {
entrypoint: fileName,
goldenFile: path.join(goldenDir, path.relative(rootDir, fileName))

View File

@ -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 * as fs from 'fs';
import * as path from 'path';

View File

@ -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 ts from 'typescript';
@ -37,8 +45,8 @@ export function publicApi(fileName: string, options: SerializationOptions = {}):
}
export function publicApiInternal(
host: ts.CompilerHost, fileName: string, tsOptions: ts.CompilerOptions,
options: SerializationOptions = {}): string {
host: ts.CompilerHost, fileName: string, tsOptions: ts.CompilerOptions,
options: SerializationOptions = {}): string {
const entrypoint = path.normalize(fileName);
if (!entrypoint.match(/\.d\.ts$/)) {
@ -50,7 +58,7 @@ export function publicApiInternal(
}
interface Diagnostic {
type: DiagnosticSeverity;
type?: DiagnosticSeverity;
message: string;
}
@ -87,7 +95,8 @@ class ResolvedDeclarationEmitter {
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) {
this.diagnostics.push({
type: 'warn',
@ -115,11 +124,11 @@ class ResolvedDeclarationEmitter {
const match = stabilityAnnotationPattern.exec(trivia);
if (match) {
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({
type: this.options.onStabilityMissing,
message: createErrorMessage(
decl, `No stability annotation found for symbol "${symbol.name}"`)
decl, `No stability annotation found for symbol "${symbol.name}"`)
});
}
@ -129,7 +138,7 @@ class ResolvedDeclarationEmitter {
this.diagnostics.push({
type: 'warn',
message:
createErrorMessage(decl, `No export declaration found for symbol "${symbol.name}"`)
createErrorMessage(decl, `No export declaration found for symbol "${symbol.name}"`)
});
}
}
@ -161,8 +170,8 @@ class ResolvedDeclarationEmitter {
return s;
}
throw new Error(
`Symbol "${resolvedSymbol.name}" was aliased as "${s.name}". ` +
`Aliases are not supported."`);
`Symbol "${resolvedSymbol.name}" was aliased as "${s.name}". ` +
`Aliases are not supported."`);
}
return resolvedSymbol;
@ -177,28 +186,31 @@ class ResolvedDeclarationEmitter {
return '';
}
const firstQualifier: ts.Identifier = getFirstQualifier(node);
const firstQualifier: ts.Identifier|null = getFirstQualifier(node);
if (firstQualifier) {
let isAllowed = false;
// Try to resolve the qualifier.
const resolvedSymbol = this.typeChecker.getSymbolAtLocation(firstQualifier);
if (resolvedSymbol && resolvedSymbol.declarations.length > 0) {
// If the qualifier can be resolved, and it's not a namespaced import, then it should be allowed.
isAllowed = resolvedSymbol.declarations.every(decl => decl.kind !== ts.SyntaxKind.NamespaceImport);
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.
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.
isAllowed = isAllowed || !(!this.options.allowModuleIdentifiers ||
this.options.allowModuleIdentifiers.indexOf(firstQualifier.text) < 0);
isAllowed = isAllowed ||
!(!this.options.allowModuleIdentifiers ||
this.options.allowModuleIdentifiers.indexOf(firstQualifier.text) < 0);
if (!isAllowed) {
this.diagnostics.push({
type: 'error',
message: createErrorMessage(
firstQualifier,
`Module identifier "${firstQualifier.text}" is not allowed. Remove it ` +
`from source or whitelist it via --allowModuleIdentifiers.`)
firstQualifier,
`Module identifier "${firstQualifier.text}" is not allowed. Remove it ` +
`from source or whitelist it via --allowModuleIdentifiers.`)
});
}
}
@ -220,15 +232,14 @@ class ResolvedDeclarationEmitter {
children.sort((a: ts.NamedDeclaration, b: ts.NamedDeclaration) => {
// Static after normal
return compareFunction(
hasModifier(a, ts.SyntaxKind.StaticKeyword),
hasModifier(b, ts.SyntaxKind.StaticKeyword)
) ||
// Our predefined order
compareFunction(
memberDeclarationOrder[a.kind], memberDeclarationOrder[b.kind]) ||
// Alphebetical order
// We need safe dereferencing due to edge cases, e.g. having two call signatures
compareFunction((a.name || a).getText(), (b.name || b).getText());
hasModifier(a, ts.SyntaxKind.StaticKeyword),
hasModifier(b, ts.SyntaxKind.StaticKeyword)) ||
// Our predefined order
compareFunction(
memberDeclarationOrder[a.kind], memberDeclarationOrder[b.kind]) ||
// Alphebetical order
// We need safe dereferencing due to edge cases, e.g. having two call signatures
compareFunction((a.name || a).getText(), (b.name || b).getText());
});
}
break;
@ -236,10 +247,9 @@ class ResolvedDeclarationEmitter {
}
}
let output = children
.filter(x => x.kind !== ts.SyntaxKind.JSDocComment)
.map(n => this.emitNode(n))
.join('');
let output: string = children.filter(x => x.kind !== ts.SyntaxKind.JSDocComment)
.map(n => this.emitNode(n))
.join('');
// Print stability annotation for fields
if (node.kind in memberDeclarationOrder) {
@ -273,7 +283,7 @@ function compareFunction<T>(a: T, b: T) {
return a === b ? 0 : a > b ? 1 : -1;
}
const memberDeclarationOrder = {
const memberDeclarationOrder: {[key: number]: number} = {
[ts.SyntaxKind.PropertySignature]: 0,
[ts.SyntaxKind.PropertyDeclaration]: 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.
*/
function getFirstQualifier(node: ts.Node): ts.Identifier {
function getFirstQualifier(node: ts.Node): ts.Identifier|null {
switch (node.kind) {
case ts.SyntaxKind.PropertyAccessExpression: {
// For expression position
@ -324,7 +334,7 @@ function createErrorMessage(node: ts.Node, message: string): string {
const sourceFile = node.getSourceFile();
let position;
if (sourceFile) {
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
const {line, character} = sourceFile.getLineAndCharacterOfPosition(node.getStart());
position = `${sourceFile.fileName}(${line + 1},${character + 1})`;
} else {
position = '<unknown>';

View File

@ -33,14 +33,11 @@
"gulp-typescript": "^4.0.1",
"gulp-util": "^3.0.8",
"merge2": "^1.2.1",
"jasmine": "^3.1.0",
"source-map": "^0.7.1",
"source-map-support": "^0.5.3",
"typescript": "~2.6.2"
},
"scripts": {
"prepublish": "gulp compile",
"test": "gulp test.unit"
},
"repository": {},
"keywords": [
"typescript"
@ -54,7 +51,7 @@
],
"license": "MIT",
"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"
}

View 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'));

View File

@ -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 * as child_process from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
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', () => {
const outDir = path.resolve(__dirname, '../../build/tmp');
const outDir = path.join(process.env['TEST_TMPDIR'], 'tmp');
beforeEach(() => {
if (!fs.existsSync(outDir)) {
@ -29,8 +37,8 @@ describe('cli: e2e test', () => {
it('should generate golden file with --out', () => {
const simpleFile = path.join(outDir, 'simple.d.ts');
const {status} = execute(['--out', simpleFile, 'test/fixtures/simple.d.ts']);
chai.assert.equal(status, 0);
const {status, stderr} = execute(['--out', simpleFile, 'test/fixtures/simple.d.ts']);
chai.assert.equal(status, 0, stderr);
assertFileEqual(simpleFile, 'test/fixtures/simple_expected.d.ts');
});
@ -117,7 +125,7 @@ describe('cli: e2e test', () => {
stderr,
'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');
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} {
const output = child_process.spawnSync(BINARY, args);
chai.assert(!output.error, 'Child process failed or timed out');
const output = child_process.spawnSync(process.execPath, [path.resolve(BINARY), ...args], {
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}`);
return {

View File

@ -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 * as ts from 'typescript';
import {parseArguments, generateFileNamePairs} from '../lib/cli';

View File

@ -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
*/

View File

@ -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 fs from 'fs';
import * as path from 'path';

View File

@ -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 fs from 'fs';
import * as path from 'path';
@ -5,7 +13,7 @@ import * as main from '../lib/main';
import {assertFileEqual, unlinkRecursively} from './helpers';
describe('integration test: public api', () => {
let _warn = null;
let _warn: any = null;
let warnings: string[] = [];
beforeEach(() => {
_warn = console.warn;
@ -71,7 +79,7 @@ describe('integration test: public api', () => {
});
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 deepOutFile = path.join(outDir, 'a/b/c/out.d.ts');

View File

@ -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 ts from 'typescript';
import { publicApiInternal, SerializationOptions } from '../lib/serializer';
import {SerializationOptions, publicApiInternal} from '../lib/serializer';
const classesAndInterfaces = `
export declare class A {
@ -20,7 +29,7 @@ const classesAndInterfaces = `
`;
describe('unit test', () => {
let _warn = null;
let _warn: any = null;
let warnings: string[] = [];
beforeEach(() => {
_warn = console.warn;
@ -47,7 +56,7 @@ describe('unit test', () => {
protected fb(): void;
}
`;
check({ 'file.d.ts': input }, expected);
check({'file.d.ts': input}, expected);
});
it('should ignore private props', () => {
@ -64,7 +73,7 @@ describe('unit test', () => {
protected fb: any;
}
`;
check({ 'file.d.ts': input }, expected);
check({'file.d.ts': input}, expected);
});
it('should support imports without capturing imports', () => {
@ -79,7 +88,7 @@ describe('unit test', () => {
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', () => {
@ -87,8 +96,8 @@ describe('unit test', () => {
export { A as Apple } from './classes_and_interfaces';
`;
checkThrows(
{ 'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input },
'Symbol "A" was aliased as "Apple". Aliases are not supported.');
{'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input},
'Symbol "A" was aliased as "Apple". Aliases are not supported.');
});
it('should remove reexported external symbols', () => {
@ -97,9 +106,9 @@ describe('unit test', () => {
`;
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(
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"']);
});
it('should sort exports', () => {
@ -134,7 +143,7 @@ describe('unit test', () => {
export declare type E = string;
`;
check({ 'file.d.ts': input }, expected);
check({'file.d.ts': input}, expected);
});
it('should sort class members', () => {
@ -158,7 +167,7 @@ describe('unit test', () => {
static foo(): void;
}
`;
check({ 'file.d.ts': input }, expected);
check({'file.d.ts': input}, expected);
});
it('should sort interface members', () => {
@ -180,7 +189,7 @@ describe('unit test', () => {
c(): void;
}
`;
check({ 'file.d.ts': input }, expected);
check({'file.d.ts': input}, expected);
});
it('should sort class members including readonly', () => {
@ -215,7 +224,7 @@ describe('unit test', () => {
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', () => {
@ -231,7 +240,7 @@ describe('unit test', () => {
(b: number): void;
}
`;
check({ 'file.d.ts': input }, expected);
check({'file.d.ts': input}, expected);
});
it('should sort exports including re-exports', () => {
@ -269,7 +278,7 @@ describe('unit test', () => {
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', () => {
@ -289,7 +298,7 @@ describe('unit test', () => {
export declare function foo(): boolean;
`;
check({ 'file.d.ts': input }, expected);
check({'file.d.ts': input}, expected);
});
it('should remove class and field comments', () => {
@ -314,7 +323,7 @@ describe('unit test', () => {
name: string;
}
`;
check({ 'file.d.ts': input }, expected);
check({'file.d.ts': input}, expected);
});
it('should skip symbols matching specified pattern', () => {
@ -327,7 +336,7 @@ describe('unit test', () => {
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', () => {
@ -337,8 +346,8 @@ describe('unit test', () => {
}
`;
checkThrows(
{ '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.');
{'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.');
});
it('should throw on using non-whitelisted module imports in type position', () => {
@ -347,8 +356,8 @@ describe('unit test', () => {
export type A = foo.A;
`;
checkThrows(
{ '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.');
{'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.');
});
it('should not throw on using whitelisted module imports', () => {
@ -361,7 +370,7 @@ describe('unit test', () => {
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', () => {
@ -374,7 +383,7 @@ describe('unit test', () => {
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', () => {
@ -404,7 +413,7 @@ describe('unit test', () => {
/** @stable */
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', () => {
@ -431,7 +440,7 @@ describe('unit test', () => {
/** @deprecated */ foo(): void;
}
`;
check({ 'file.d.ts': input }, expected);
check({'file.d.ts': input}, expected);
});
it('should warn on onStabilityMissing: warn', () => {
@ -445,20 +454,20 @@ describe('unit test', () => {
constructor();
}
`;
check({ 'file.d.ts': input }, expected, { onStabilityMissing: 'warn' });
check({'file.d.ts': input}, expected, {onStabilityMissing: 'warn'});
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 {
getSourceFile: (sourceName, languageVersion) => {
if (!files[sourceName]) return undefined;
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],
readFile: (filename) => stripExtraIndentation(files[filename]),
getDefaultLibFileName: () => 'lib.ts',
@ -471,12 +480,12 @@ function getMockHost(files: { [name: string]: string }): ts.CompilerHost {
}
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);
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);
}
@ -485,7 +494,7 @@ function stripExtraIndentation(text: string) {
// Ignore first and last new line
lines = lines.slice(1, lines.length - 1);
const commonIndent = lines.reduce((min, line) => {
const indent = /^( *)/.exec(line)[1].length;
const indent = /^( *)/.exec(line) ![1].length;
// Ignore empty line
return line.length ? Math.min(min, indent) : min;
}, text.length);

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,7 @@
"exclude": [
"testing",
"node_modules",
"ts-api-guardian",
"typings-test",
"public_api_guard",
"docs"

View File

@ -288,9 +288,9 @@ ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
ansi-styles@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
dependencies:
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"
chalk@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796"
version "2.3.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65"
dependencies:
ansi-styles "^3.2.0"
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.2.0"
supports-color "^5.3.0"
char-spinner@^1.0.1:
version "1.0.1"
@ -6871,9 +6871,9 @@ supports-color@^3.1.0:
dependencies:
has-flag "^1.0.0"
supports-color@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a"
supports-color@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.3.0.tgz#5b24ac15db80fa927cf5227a4a33fd3c4c7676c0"
dependencies:
has-flag "^3.0.0"