diff --git a/.circleci/config.yml b/.circleci/config.yml index af0c7b7a4a..dde909853c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -798,11 +798,11 @@ jobs: - setup_win - run: name: Build all windows CI targets - command: yarn bazel build --build_tag_filters=-ivy-only //packages/compiler-cli/... //tools/ts-api-guardian/... + command: yarn bazel build --build_tag_filters=-ivy-only //packages/compiler-cli/... no_output_timeout: 15m - run: name: Test all windows CI targets - command: yarn bazel test --test_tag_filters="-ivy-only,-browser:chromium-local" //packages/compiler-cli/... //tools/ts-api-guardian/... + command: yarn bazel test --test_tag_filters="-ivy-only,-browser:chromium-local" //packages/compiler-cli/... no_output_timeout: 15m test_ivy_aot_win: @@ -811,11 +811,11 @@ jobs: - setup_win - run: name: Build all windows CI targets - command: yarn bazel build --config=ivy --build_tag_filters=-no-ivy-aot,-fixme-ivy-aot //packages/compiler-cli/... //tools/ts-api-guardian/... + command: yarn bazel build --config=ivy --build_tag_filters=-no-ivy-aot,-fixme-ivy-aot //packages/compiler-cli/... no_output_timeout: 15m - run: name: Test all windows CI targets - command: yarn bazel test --config=ivy --test_tag_filters="-no-ivy-aot,-fixme-ivy-aot,-browser:chromium-local" //packages/compiler-cli/... //tools/ts-api-guardian/... //packages/localize/... + command: yarn bazel test --config=ivy --test_tag_filters="-no-ivy-aot,-fixme-ivy-aot,-browser:chromium-local" //packages/compiler-cli/... //packages/localize/... no_output_timeout: 15m # Save dependencies to use on subsequent runs. - save_cache: diff --git a/.gitattributes b/.gitattributes index b45635737c..0acf0cd70d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,8 +5,5 @@ *.js eol=lf *.ts eol=lf -# API guardian patch must always use LF for tests to work -*.patch eol=lf - # Must keep Windows line ending to be parsed correctly scripts/windows/packages.txt eol=crlf diff --git a/.pullapprove.yml b/.pullapprove.yml index e49c09546b..ad0c4bbf41 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -1177,7 +1177,6 @@ groups: 'tools/source-map-test/**', 'tools/symbol-extractor/**', 'tools/testing/**', - 'tools/ts-api-guardian/**', 'tools/tslint/**', 'tools/utils/**', 'tools/yarn/**', diff --git a/renovate.json b/renovate.json index 06d7f9a115..48c89222d7 100644 --- a/renovate.json +++ b/renovate.json @@ -53,7 +53,6 @@ "integration/bazel/WORKSPACE", "package.json", "packages/**/package.json", - "tools/ts-api-guardian/package.json", "aio/package.json" ], "packageRules": [ diff --git a/tools/ts-api-guardian/BUILD.bazel b/tools/ts-api-guardian/BUILD.bazel deleted file mode 100644 index d408c31f5d..0000000000 --- a/tools/ts-api-guardian/BUILD.bazel +++ /dev/null @@ -1,115 +0,0 @@ -# BEGIN-INTERNAL -load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm") -load("@npm//@bazel/typescript:index.bzl", "ts_library") -load("//tools:defaults.bzl", "jasmine_node_test") - -ts_library( - name = "lib", - srcs = glob(["lib/*.ts"]), - module_name = "ts-api-guardian", - tsconfig = "//tools:tsconfig.json", - visibility = ["//visibility:public"], - deps = [ - "@npm//@types/diff", - "@npm//@types/minimist", - "@npm//@types/node", - "@npm//chalk", - "@npm//diff", - "@npm//minimist", - "@npm//typescript", - ], -) - -# 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 $< $@", -) - -pkg_npm( - name = "ts-api-guardian", - package_name = "ts-api-guardian", - srcs = [ - "BUILD.bazel", - "README.md", - "bin/ts-api-guardian", - "index.bzl", - "package.json", - ], - substitutions = { - "@angular//tools/ts-api-guardian:bin": "//:node_modules/ts-api-guardian/bin", - "@angular//tools/ts-api-guardian:lib": "@npm//ts-api-guardian", - }, - deps = [ - ":lib", - ":license", - ], -) - -#######################################3 -# Tests for this package - -ts_library( - name = "test_lib", - testonly = True, - srcs = glob( - ["test/*.ts"], - exclude = ["test/bootstrap.ts"], - ), - tsconfig = "//tools:tsconfig-test", - deps = [ - ":lib", - "@npm//@bazel/runfiles", - "@npm//@types/jasmine", - "@npm//@types/node", - "@npm//jasmine", - "@npm//typescript", - ], -) - -ts_library( - name = "bootstrap", - testonly = True, - srcs = ["test/bootstrap.ts"], - tsconfig = "//tools:tsconfig-test", - deps = ["@npm//@types/node"], -) - -# Select the es5 .js output of the ts_library :boostrap target -# with `output_group = "es5_sources"` for use in the jasmine_node_test -# below. This exposes an internal detail of ts_library that is not ideal. -# TODO(gregmagolan): clean this up by using tsc() in this case rather than ts_library -filegroup( - name = "bootstrap_es5", - testonly = True, - srcs = [":bootstrap"], - output_group = "es5_sources", -) - -jasmine_node_test( - name = "tests", - srcs = [ - ":test_lib", - ], - bootstrap = [":bootstrap_es5"], - data = glob([ - "test/fixtures/*.ts", - "test/fixtures/*.patch", - ]) + [ - ":ts-api-guardian", - ], -) - -filegroup( - name = "bin", - srcs = glob(["lib/*.js"]) + ["bin/ts-api-guardian"], - visibility = ["//visibility:public"], -) - -# Exported to be referenced as entry_point of the nodejs_binary -exports_files(["bin/ts-api-guardian"]) - -# END-INTERNAL diff --git a/tools/ts-api-guardian/README.md b/tools/ts-api-guardian/README.md deleted file mode 100644 index 296d4e1e6f..0000000000 --- a/tools/ts-api-guardian/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Typescript API Guardian - -Keeps track of public API surface of a typescript library. - -Examples: - -```sh -# Generate one declaration file -ts-api-guardian --out api_guard.d.ts index.d.ts -# Generate multiple declaration files -# (output location like typescript) -ts-api-guardian --outDir api_guard [--rootDir .] core/index.d.ts core/testing.d.ts -# Print usage -ts-api-guardian --help -# Check against one declaration file -ts-api-guardian --verify api_guard.d.ts index.d.ts -# Check against multiple declaration files -ts-api-guardian --verifyDir api_guard [--rootDir .] core/index.d.ts core/testing.d.ts -``` - -# For developers - -Build and test this library: - -```sh -$ yarn bazel run //:install -$ yarn bazel test //tools/ts-api-guardian:all -``` - -Publish to NPM: - -```sh -$ yarn bazel run @nodejs//:npm whoami # should be logged in as angular -$ grep version tools/ts-api-guardian/package.json # advance as needed -$ yarn bazel run //tools/ts-api-guardian:ts-api-guardian.publish -``` diff --git a/tools/ts-api-guardian/bin/ts-api-guardian b/tools/ts-api-guardian/bin/ts-api-guardian deleted file mode 100755 index b0cfde463b..0000000000 --- a/tools/ts-api-guardian/bin/ts-api-guardian +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node - -require('../lib/cli').startCli(); diff --git a/tools/ts-api-guardian/index.bzl b/tools/ts-api-guardian/index.bzl deleted file mode 100644 index f6ed5b311d..0000000000 --- a/tools/ts-api-guardian/index.bzl +++ /dev/null @@ -1,148 +0,0 @@ -# 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//:index.bzl", "nodejs_binary", "nodejs_test") - -COMMON_MODULE_IDENTIFIERS = ["angular", "jasmine", "protractor", "Symbol"] - -def ts_api_guardian_test( - name, - golden, - actual, - data = [], - strip_export_pattern = [], - allow_module_identifiers = COMMON_MODULE_IDENTIFIERS, - use_angular_tag_rules = True, - **kwargs): - """Runs ts_api_guardian - """ - data += [ - # Locally we need to add the TS build target - # But it will replaced to @npm//ts-api-guardian when publishing - "@angular//tools/ts-api-guardian:lib", - # BEGIN-INTERNAL - "@angular//tools/ts-api-guardian:bin", - # END-INTERNAL - # The below are required during runtime - "@npm//chalk", - "@npm//diff", - "@npm//minimist", - "@npm//typescript", - ] - - args = [ - # Needed so that node doesn't walk back to the source directory. - # From there, the relative imports would point to .ts files. - "--node_options=--preserve-symlinks", - # TODO(josephperrott): update dependency usages to no longer need bazel patch module resolver - # See: https://github.com/bazelbuild/rules_nodejs/wiki#--bazel_patch_module_resolver-now-defaults-to-false-2324 - "--bazel_patch_module_resolver", - ] - - for i in strip_export_pattern: - # Quote the regexp before passing it via the command line. - quoted_pattern = "\"%s\"" % i - args += ["--stripExportPattern", quoted_pattern] - - for i in allow_module_identifiers: - args += ["--allowModuleIdentifiers", i] - - if use_angular_tag_rules: - args += ["--useAngularTagRules"] - - nodejs_test( - name = name, - data = data, - entry_point = Label("@angular//tools/ts-api-guardian:bin/ts-api-guardian"), - tags = kwargs.pop("tags", []) + ["api_guard"], - templated_args = args + ["--verify", golden, actual], - **kwargs - ) - - nodejs_binary( - name = name + ".accept", - testonly = True, - data = data, - entry_point = Label("@angular//tools/ts-api-guardian:bin/ts-api-guardian"), - tags = kwargs.pop("tags", []) + ["api_guard"], - templated_args = args + ["--out", golden, actual], - **kwargs - ) - -def ts_api_guardian_test_npm_package( - name, - goldenDir, - actualDir, - data = [], - strip_export_pattern = ["^ɵ(?!ɵdefineInjectable|ɵinject|ɵInjectableDef)"], - allow_module_identifiers = COMMON_MODULE_IDENTIFIERS, - use_angular_tag_rules = True, - **kwargs): - """Runs ts_api_guardian - """ - data += [ - # Locally we need to add the TS build target - # But it will replaced to @npm//ts-api-guardian when publishing - "@angular//tools/ts-api-guardian:lib", - "@angular//tools/ts-api-guardian:bin", - # The below are required during runtime - "@npm//chalk", - "@npm//diff", - "@npm//minimist", - "@npm//typescript", - ] - - args = [ - # Needed so that node doesn't walk back to the source directory. - # From there, the relative imports would point to .ts files. - "--node_options=--preserve-symlinks", - # We automatically discover the enpoints for our NPM package. - "--autoDiscoverEntrypoints", - # TODO(josephperrott): update dependency usages to no longer need bazel patch module resolver - # See: https://github.com/bazelbuild/rules_nodejs/wiki#--bazel_patch_module_resolver-now-defaults-to-false-2324 - "--bazel_patch_module_resolver", - ] - - for i in strip_export_pattern: - # Quote the regexp before passing it via the command line. - quoted_pattern = "\"%s\"" % i - args += ["--stripExportPattern", quoted_pattern] - - for i in allow_module_identifiers: - args += ["--allowModuleIdentifiers", i] - - if use_angular_tag_rules: - args += ["--useAngularTagRules"] - - nodejs_test( - name = name, - data = data, - entry_point = "@angular//tools/ts-api-guardian:bin/ts-api-guardian", - tags = kwargs.pop("tags", []) + ["api_guard"], - templated_args = args + ["--autoDiscoverEntrypoints", "--verifyDir", goldenDir, "--rootDir", "$(rlocation %s)" % actualDir], - **kwargs - ) - - nodejs_binary( - name = name + ".accept", - testonly = True, - data = data, - entry_point = "@angular//tools/ts-api-guardian:bin/ts-api-guardian", - tags = kwargs.pop("tags", []) + ["api_guard"], - templated_args = args + ["--autoDiscoverEntrypoints", "--outDir", goldenDir, "--rootDir", "$(rlocation %s)" % actualDir], - **kwargs - ) diff --git a/tools/ts-api-guardian/lib/cli.ts b/tools/ts-api-guardian/lib/cli.ts deleted file mode 100644 index 0ff5cebd5e..0000000000 --- a/tools/ts-api-guardian/lib/cli.ts +++ /dev/null @@ -1,279 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 -import * as chalk from 'chalk'; -import * as minimist from 'minimist'; -import * as path from 'path'; - -import {discoverAllEntrypoints, generateGoldenFile, SerializationOptions, verifyAgainstGoldenFile} from './main'; - -/** Name of the CLI */ -const CMD = 'ts-api-guardian'; - -/** Name of the Bazel workspace that runs the CLI. */ -const bazelWorkspaceName = process.env.BAZEL_WORKSPACE; -/** - * Path to the Bazel workspace directory. Only set if the CLI is run with `bazel run`. - * https://docs.bazel.build/versions/master/user-manual.html#run. - */ -const bazelWorkspaceDirectory = process.env.BUILD_WORKSPACE_DIRECTORY; -/** - * Regular expression that matches Bazel manifest paths that start with the - * current Bazel workspace, followed by a path delimiter. - */ -const bazelWorkspaceManifestPathRegex = - bazelWorkspaceName ? new RegExp(`^${bazelWorkspaceName}[/\\\\]`) : null; - -export function startCli() { - const {argv, mode, errors} = parseArguments(process.argv.slice(2)); - - const options: SerializationOptions = { - stripExportPattern: [].concat(argv['stripExportPattern']), - allowModuleIdentifiers: [].concat(argv['allowModuleIdentifiers']), - }; - - // Since the API guardian can be also used by other projects, we should not set up the default - // Angular project tag rules unless specified explicitly through a given option. - if (argv['useAngularTagRules']) { - options.exportTags = { - requireAtLeastOne: ['publicApi', 'codeGenApi'], - banned: ['experimental'], - toCopy: ['deprecated', 'codeGenApi'] - }; - options.memberTags = { - requireAtLeastOne: [], - banned: ['experimental', 'publicApi', 'codeGenApi'], - toCopy: ['deprecated'] - }; - options.paramTags = { - requireAtLeastOne: [], - banned: ['experimental', 'publicApi', 'codeGenApi'], - toCopy: ['deprecated'] - }; - } - - // In autoDiscoverEntrypoints mode we set the inputed files as the discovered entrypoints - // for the rootDir - let entrypoints: string[]; - if (argv['autoDiscoverEntrypoints']) { - entrypoints = discoverAllEntrypoints(argv['rootDir']); - } else { - entrypoints = argv._.slice(); - } - - for (const error of errors) { - console.warn(error); - } - - if (mode === 'help') { - printUsageAndExit(!!errors.length); - } else { - const targets = resolveFileNamePairs(argv, mode, entrypoints); - - if (mode === 'out') { - for (const {entrypoint, goldenFile} of targets) { - generateGoldenFile(entrypoint, goldenFile, options); - } - } else { // mode === 'verify' - let hasDiff = false; - - for (const {entrypoint, goldenFile} of targets) { - const diff = verifyAgainstGoldenFile(entrypoint, goldenFile, options); - if (diff) { - hasDiff = true; - const lines = diff.split('\n'); - if (lines.length) { - lines.pop(); // Remove trailing newline - } - for (const line of lines) { - const chalkMap: - {[key: string]: any} = {'-': chalk.red, '+': chalk.green, '@': chalk.cyan}; - const chalkFunc = chalkMap[line[0]] || chalk.reset; - console.log(chalkFunc(line)); - } - } - } - - if (hasDiff) { - const bazelTarget = process.env['BAZEL_TARGET']; - // Under bazel, give instructions how to use bazel run to accept the golden file. - if (bazelTarget) { - console.error('\n\nIf you modify a public API, you must accept the new golden file.'); - console.error('\n\nTo do so, execute the following Bazel target:'); - console.error(` yarn bazel run ${bazelTarget.replace(/_bin$/, '')}.accept`); - if (process.env['TEST_WORKSPACE'] === 'angular') { - console.error('\n\nFor more information, see'); - console.error( - '\n https://github.com/angular/angular/blob/master/docs/PUBLIC_API.md#golden-files'); - } - } - - process.exit(1); - } - } - } -} - -export function parseArguments(input: string[]): - {argv: minimist.ParsedArgs, mode: string, errors: string[]} { - let help = false; - const errors: string[] = []; - - const argv = minimist(input, { - string: [ - 'out', 'outDir', 'verify', 'verifyDir', 'rootDir', 'stripExportPattern', - 'allowModuleIdentifiers' - ], - boolean: [ - 'help', 'useAngularTagRules', 'autoDiscoverEntrypoints', - // Options used by chalk automagically - 'color', 'no-color' - ], - alias: {'outFile': 'out', 'verifyFile': 'verify'}, - unknown: (option: string) => { - if (option[0] === '-') { - errors.push(`Unknown option: ${option}`); - help = true; - return false; // do not add to argv._ - } else { - return true; // add to argv._ - } - } - }); - - help = help || argv['help']; - - if (help) { - return {argv, mode: 'help', errors}; - } - - let modes: string[] = []; - - if (argv['out']) { - modes.push('out'); - } - if (argv['outDir']) { - modes.push('out'); - } - if (argv['verify']) { - modes.push('verify'); - } - if (argv['verifyDir']) { - modes.push('verify'); - } - - if (argv['autoDiscoverEntrypoints']) { - if (!argv['rootDir']) { - errors.push(`--rootDir must be provided with --autoDiscoverEntrypoints.`); - modes = ['help']; - } - if (!argv['outDir'] && !argv['verifyDir']) { - errors.push(`--outDir or --verifyDir must be used with --autoDiscoverEntrypoints.`); - modes = ['help']; - } - } else { - if (!argv._.length) { - errors.push('No input file specified.'); - modes = ['help']; - } else if (modes.length !== 1) { - errors.push('Specify either --out[Dir] or --verify[Dir]'); - modes = ['help']; - } else if (argv._.length > 1 && !argv['outDir'] && !argv['verifyDir']) { - errors.push(`More than one input specified. Use --${modes[0]}Dir instead.`); - modes = ['help']; - } - } - - return {argv, mode: modes[0], errors}; -} - -function printUsageAndExit(error = false) { - const print = error ? console.warn.bind(console) : console.log.bind(console); - print(`Usage: ${CMD} [options] - ${CMD} --out - ${CMD} --outDir [--rootDir .] - - ${CMD} --verify - ${CMD} --verifyDir [--rootDir .] - -Options: - --help Show this usage message - - --out Write golden output to file - --outDir Write golden file structure to directory - - --verify Read golden input from file - --verifyDir Read golden file structure from directory - - --rootDir Specify the root directory of input files - - --useAngularTagRules Whether the Angular specific tag rules should be used. - --stripExportPattern Do not output exports matching the pattern - --allowModuleIdentifiers - Allow identifier for "* as foo" imports - --autoDiscoverEntrypoints Automatically find all entrypoints .d.ts files in the rootDir`); - process.exit(error ? 1 : 0); -} - -/** - * Resolves a given path in the file system. If `ts-api-guardian` runs with Bazel, file paths - * are resolved through runfiles. Additionally in Bazel, this method handles the case where - * manifest file paths are not existing, but need to resolve to the Bazel workspace directory. - * This happens commonly when goldens are approved, but the golden file does not exist yet. - */ -function resolveFilePath(fileName: string): string { - // If an absolute path is specified, the path is already resolved. - if (path.isAbsolute(fileName)) { - return fileName; - } - // Outside of Bazel, file paths are resolved based on the current working directory. - if (!bazelWorkspaceName) { - return path.resolve(fileName); - } - // In Bazel, we first try to resolve the file through the runfiles. We do this by calling - // the `require.resolve` function that is patched by the Bazel NodeJS rules. Note that we - // need to catch errors because files inside tree artifacts cannot be resolved through - // runfile manifests. Hence, we need to have alternative resolution logic when resolving - // file paths. Additionally, it could happen that manifest paths which aren't part of the - // runfiles are specified (i.e. golden is approved but does not exist in the workspace yet). - try { - return require.resolve(fileName); - } catch { - } - // This handles cases where file paths cannot be resolved through runfiles. This happens - // commonly when goldens are approved while the golden does not exist in the workspace yet. - // In those cases, we want to build up a relative path based on the manifest path, and join - // it with the absolute bazel workspace directory (which is only set in `bazel run`). - // e.g. `angular/goldens/<..>/common` should become `{workspace_dir}/goldens/<...>/common`. - if (bazelWorkspaceManifestPathRegex !== null && bazelWorkspaceDirectory && - bazelWorkspaceManifestPathRegex.test(fileName)) { - return path.join(bazelWorkspaceDirectory, fileName.substr(bazelWorkspaceName.length + 1)); - } - throw Error(`Could not resolve file path in runfiles: ${fileName}`); -} - -function resolveFileNamePairs(argv: minimist.ParsedArgs, mode: string, entrypoints: string[]): - {entrypoint: string, goldenFile: string}[] { - if (argv[mode]) { - return [{ - entrypoint: resolveFilePath(entrypoints[0]), - goldenFile: resolveFilePath(argv[mode]), - }]; - } else { // argv[mode + 'Dir'] - let rootDir = argv['rootDir'] || '.'; - const goldenDir = argv[mode + 'Dir']; - - return entrypoints.map((fileName: string) => { - return { - entrypoint: resolveFilePath(fileName), - goldenFile: resolveFilePath(path.join(goldenDir, path.relative(rootDir, fileName))), - }; - }); - } -} diff --git a/tools/ts-api-guardian/lib/main.ts b/tools/ts-api-guardian/lib/main.ts deleted file mode 100644 index 777bce97c1..0000000000 --- a/tools/ts-api-guardian/lib/main.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @license - * Copyright Google LLC 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'; - -import {publicApi, SerializationOptions} from './serializer'; - -export {publicApi, SerializationOptions} from './serializer'; - -export function generateGoldenFile( - entrypoint: string, outFile: string, options: SerializationOptions = {}): void { - const output = publicApi(entrypoint, options); - - ensureDirectory(path.dirname(outFile)); - fs.writeFileSync(outFile, output); -} - -export function verifyAgainstGoldenFile( - entrypoint: string, goldenFile: string, options: SerializationOptions = {}): string { - const actual = publicApi(entrypoint, options); - const expected = fs.existsSync(goldenFile) ? fs.readFileSync(goldenFile).toString() : ''; - - if (actual === expected) { - return ''; - } else { - // The patch should not show absolute paths, as these are pretty long and obfuscated - // the printed golden diff. Additionally, path separators in the patch should be forward - // slashes for consistency and to enable easier integration testing. - const displayFileName = path.relative(process.cwd(), goldenFile).replace(/\\/g, '/'); - const patch = createPatch(displayFileName, expected, actual, 'Golden file', 'Generated API'); - - // Remove the header of the patch - const start = patch.indexOf('\n', patch.indexOf('\n') + 1) + 1; - - return patch.substring(start); - } -} - -function ensureDirectory(dir: string) { - if (!fs.existsSync(dir)) { - ensureDirectory(path.dirname(dir)); - fs.mkdirSync(dir); - } -} - -/** - * Determine if the provided path is a directory. - */ -function isDirectory(dirPath: string) { - try { - return fs.lstatSync(dirPath).isDirectory(); - } catch { - return false; - } -} - -/** - * Gets an array of paths to the typings files for each of the recursively discovered - * package.json - * files from the directory provided. - */ -export function discoverAllEntrypoints(dirPath: string) { - // Determine all of the package.json files - const packageJsons: string[] = []; - const entryPoints: string[] = []; - const findPackageJsonsInDir = (nextPath: string) => { - for (const file of fs.readdirSync(nextPath)) { - const fullPath = path.join(nextPath, file); - if (isDirectory(fullPath)) { - findPackageJsonsInDir(fullPath); - } else { - if (file === 'package.json') { - packageJsons.push(fullPath); - } - } - } - }; - findPackageJsonsInDir(dirPath); - - // Get all typings file locations from package.json files - for (const packageJson of packageJsons) { - const packageJsonObj = - JSON.parse(fs.readFileSync(packageJson, {encoding: 'utf8'})) as {typings: string}; - const typings = packageJsonObj.typings; - if (typings) { - entryPoints.push(path.join(path.dirname(packageJson), typings)); - } - } - - return entryPoints; -} diff --git a/tools/ts-api-guardian/lib/serializer.ts b/tools/ts-api-guardian/lib/serializer.ts deleted file mode 100644 index ca5ac52116..0000000000 --- a/tools/ts-api-guardian/lib/serializer.ts +++ /dev/null @@ -1,448 +0,0 @@ -/** - * @license - * Copyright Google LLC 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'; - -const baseTsOptions: ts.CompilerOptions = { - // We don't want symbols from external modules to be resolved, so we use the - // classic algorithm. - moduleResolution: ts.ModuleResolutionKind.Classic -}; - -export interface JsDocTagOptions { - /** - * An array of names of jsdoc tags, one of which must exist. If no tags are provided, there are no - * required tags. - */ - requireAtLeastOne?: string[]; - - /** - * An array of names of jsdoc tags that must not exist. - */ - banned?: string[]; - - /** - * An array of names of jsdoc tags that will be copied to the serialized code. - */ - toCopy?: string[]; -} - -export interface SerializationOptions { - /** - * Removes all exports matching the regular expression. - */ - stripExportPattern?: RegExp|RegExp[]; - /** - * Allows these identifiers as modules in the output. For example, - * ``` - * import * as angular from './angularjs'; - * - * export class Foo extends angular.Bar {} - * ``` - * will produce `export class Foo extends angular.Bar {}` and requires explicitly allowing - * `angular` as a module identifier. - */ - allowModuleIdentifiers?: string[]; - - /** The jsdoc tag options for top level exports */ - exportTags?: JsDocTagOptions; - - /** The jsdoc tag options for properties/methods/etc of exports */ - memberTags?: JsDocTagOptions; - - /** The jsdoc tag options for parameters of members/functions */ - paramTags?: JsDocTagOptions; -} - -export type DiagnosticSeverity = 'warn'|'error'|'none'; - -export function publicApi(fileName: string, options: SerializationOptions = {}): string { - return publicApiInternal(ts.createCompilerHost(baseTsOptions), fileName, baseTsOptions, options); -} - -export function publicApiInternal( - host: ts.CompilerHost, fileName: string, tsOptions: ts.CompilerOptions, - options: SerializationOptions = {}): string { - // Since the entry point will be compared with the source files from the TypeScript program, - // the path needs to be normalized with forward slashes in order to work within Windows. - const entrypoint = path.normalize(fileName).replace(/\\/g, '/'); - - // Setup default tag options - options = { - ...options, - exportTags: applyDefaultTagOptions(options.exportTags), - memberTags: applyDefaultTagOptions(options.memberTags), - paramTags: applyDefaultTagOptions(options.paramTags) - }; - - if (!entrypoint.match(/\.d\.ts$/)) { - throw new Error(`Source file "${fileName}" is not a declaration file`); - } - - const program = ts.createProgram([entrypoint], tsOptions, host); - return new ResolvedDeclarationEmitter(program, entrypoint, options).emit(); -} - -interface Diagnostic { - type?: DiagnosticSeverity; - message: string; -} - -class ResolvedDeclarationEmitter { - private program: ts.Program; - private fileName: string; - private typeChecker: ts.TypeChecker; - private options: SerializationOptions; - private diagnostics: Diagnostic[]; - - constructor(program: ts.Program, fileName: string, options: SerializationOptions) { - this.program = program; - this.fileName = fileName; - this.options = options; - this.diagnostics = []; - - this.typeChecker = this.program.getTypeChecker(); - } - - emit(): string { - const sourceFile = this.program.getSourceFiles().find(sf => sf.fileName === this.fileName); - if (!sourceFile) { - throw new Error(`Source file "${this.fileName}" not found`); - } - - let output: string[] = []; - - const resolvedSymbols = this.getResolvedSymbols(sourceFile); - // Sort all symbols so that the output is more deterministic - resolvedSymbols.sort(symbolCompareFunction); - - for (const symbol of resolvedSymbols) { - if (this.isExportPatternStripped(symbol.name)) { - continue; - } - - const typeDecl = symbol.declarations && symbol.declarations[0]; - const valDecl = symbol.valueDeclaration; - if (!typeDecl && !valDecl) { - this.diagnostics.push({ - type: 'warn', - message: `${sourceFile.fileName}: error: No declaration found for symbol "${symbol.name}"` - }); - continue; - } - typeDecl && this.emitDeclaration(symbol, typeDecl, output); - if (valDecl && typeDecl.kind === ts.SyntaxKind.InterfaceDeclaration) { - // Only generate value declarations in case of interfaces. - valDecl && this.emitDeclaration(symbol, valDecl, output); - } - } - - if (this.diagnostics.length) { - const message = this.diagnostics.map(d => d.message).join('\n'); - console.warn(message); - if (this.diagnostics.some(d => d.type === 'error')) { - throw new Error(message); - } - } - - return output.join(''); - } - - emitDeclaration(symbol: ts.Symbol, decl: ts.Node, output: string[]) { - // The declaration node may not be a complete statement, e.g. for var/const - // symbols. We need to find the complete export statement by traversing - // upwards. - while (!hasModifier(decl, ts.SyntaxKind.ExportKeyword) && decl.parent) { - decl = decl.parent; - } - - if (hasModifier(decl, ts.SyntaxKind.ExportKeyword)) { - // Make an empty line between two exports - if (output.length) { - output.push('\n'); - } - - const jsdocComment = this.processJsDocTags(decl, this.options.exportTags); - if (jsdocComment) { - output.push(jsdocComment + '\n'); - } - - output.push(stripEmptyLines(this.emitNode(decl)) + '\n'); - } else { - // This may happen for symbols re-exported from external modules. - this.diagnostics.push({ - type: 'warn', - message: createErrorMessage(decl, `No export declaration found for symbol "${symbol.name}"`) - }); - } - } - - private isExportPatternStripped(symbolName: string): boolean { - return [].concat(this.options.stripExportPattern).some(p => !!(p && symbolName.match(p))); - } - - private getResolvedSymbols(sourceFile: ts.SourceFile): ts.Symbol[] { - const ms = (sourceFile).symbol; - const rawSymbols = ms ? (this.typeChecker.getExportsOfModule(ms) || []) : []; - return rawSymbols.map(s => { - if (s.flags & ts.SymbolFlags.Alias) { - const resolvedSymbol = this.typeChecker.getAliasedSymbol(s); - - // This will happen, e.g. for symbols re-exported from external modules. - if (!resolvedSymbol.valueDeclaration && !resolvedSymbol.declarations) { - return s; - } - if (resolvedSymbol.name !== s.name) { - if (this.isExportPatternStripped(s.name)) { - return s; - } - throw new Error( - `Symbol "${resolvedSymbol.name}" was aliased as "${s.name}". ` + - `Aliases are not supported.`); - } - - return resolvedSymbol; - } else { - return s; - } - }); - } - - emitNode(node: ts.Node) { - if (hasModifier(node, ts.SyntaxKind.PrivateKeyword)) { - return ''; - } - - 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 && 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); - if (!isAllowed) { - this.diagnostics.push({ - type: 'error', - message: createErrorMessage( - firstQualifier, - `Module identifier "${firstQualifier.text}" is not allowed. Remove it ` + - `from source or allow it via --allowModuleIdentifiers.`) - }); - } - } - - let children: ts.Node[] = []; - if (ts.isFunctionDeclaration(node)) { - // Used ts.isFunctionDeclaration instead of node.kind because this is a type guard - const symbol = this.typeChecker.getSymbolAtLocation(node.name); - symbol.declarations.forEach(x => children = children.concat(x.getChildren())); - } else { - children = node.getChildren(); - } - - const sourceText = node.getSourceFile().text; - if (children.length) { - // Sort declarations under a class or an interface - if (node.kind === ts.SyntaxKind.SyntaxList) { - switch (node.parent && node.parent.kind) { - case ts.SyntaxKind.ClassDeclaration: - case ts.SyntaxKind.InterfaceDeclaration: { - // There can be multiple SyntaxLists under a class or an interface, - // since SyntaxList is just an arbitrary data structure generated - // by Node#getChildren(). We need to check that we are sorting the - // right list. - if (children.every(node => node.kind in memberDeclarationOrder)) { - children = children.slice(); - 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()); - }); - } - break; - } - } - } - - let output: string = children.filter(x => x.kind !== ts.SyntaxKind.JSDocComment) - .map(n => this.emitNode(n)) - .join(''); - - // Print stability annotation for fields and parmeters - if (ts.isParameter(node) || node.kind in memberDeclarationOrder) { - const tagOptions = ts.isParameter(node) ? this.options.paramTags : this.options.memberTags; - const jsdocComment = this.processJsDocTags(node, tagOptions); - if (jsdocComment) { - // Add the annotation after the leading whitespace - output = output.replace(/^(\r?\n\s*)/, `$1${jsdocComment} `); - } - } - - return output; - } else { - const ranges = ts.getLeadingCommentRanges(sourceText, node.pos); - let tail = node.pos; - for (const range of ranges || []) { - if (range.end > tail) { - tail = range.end; - } - } - return sourceText.substring(tail, node.end); - } - } - - private processJsDocTags(node: ts.Node, tagOptions: JsDocTagOptions) { - const jsDocTags = getJsDocTags(node); - const requireAtLeastOne = tagOptions.requireAtLeastOne; - const isMissingAnyRequiredTag = requireAtLeastOne != null && requireAtLeastOne.length > 0 && - jsDocTags.every(tag => requireAtLeastOne.indexOf(tag) === -1); - if (isMissingAnyRequiredTag) { - this.diagnostics.push({ - type: 'error', - message: createErrorMessage( - node, - 'Required jsdoc tags - One of the tags: ' + - requireAtLeastOne.map(tag => `"@${tag}"`).join(', ') + - ` - must exist on ${getName(node)}.`) - }); - } - const bannedTagsFound = - tagOptions.banned.filter(bannedTag => jsDocTags.some(tag => tag === bannedTag)); - if (bannedTagsFound.length) { - this.diagnostics.push({ - type: 'error', - message: createErrorMessage( - node, - 'Banned jsdoc tags - ' + bannedTagsFound.map(tag => `"@${tag}"`).join(', ') + - ` - were found on ${getName(node)}.`) - }); - } - const tagsToCopy = - jsDocTags.filter(tag => tagOptions.toCopy.some(tagToCopy => tag === tagToCopy)); - - if (tagsToCopy.length === 1) { - return `/** @${tagsToCopy[0]} */`; - } else if (tagsToCopy.length > 1) { - return '/**\n' + tagsToCopy.map(tag => ` * @${tag}`).join('\n') + ' */\n'; - } else { - return ''; - } - } -} - -const tagRegex = /@(\w+)/g; - -function getJsDocTags(node: ts.Node): string[] { - const sourceText = node.getSourceFile().text; - const trivia = sourceText.substr(node.pos, node.getLeadingTriviaWidth()); - // We use a hash so that we don't collect duplicate jsdoc tags - // (e.g. if a property has a getter and setter with the same tag). - const jsdocTags: {[key: string]: boolean} = {}; - let match: RegExpExecArray; - while (match = tagRegex.exec(trivia)) { - jsdocTags[match[1]] = true; - } - return Object.keys(jsdocTags); -} - -function symbolCompareFunction(a: ts.Symbol, b: ts.Symbol) { - return a.name.localeCompare(b.name); -} - -function compareFunction(a: T, b: T) { - return a === b ? 0 : a > b ? 1 : -1; -} - -const memberDeclarationOrder: {[key: number]: number} = { - [ts.SyntaxKind.PropertySignature]: 0, - [ts.SyntaxKind.PropertyDeclaration]: 0, - [ts.SyntaxKind.GetAccessor]: 0, - [ts.SyntaxKind.SetAccessor]: 0, - [ts.SyntaxKind.CallSignature]: 1, - [ts.SyntaxKind.Constructor]: 2, - [ts.SyntaxKind.ConstructSignature]: 2, - [ts.SyntaxKind.IndexSignature]: 3, - [ts.SyntaxKind.MethodSignature]: 4, - [ts.SyntaxKind.MethodDeclaration]: 4 -}; - -function stripEmptyLines(text: string): string { - return text.split(/\r?\n/).filter(x => !!x.length).join('\n'); -} - -/** - * Returns the first qualifier if the input node is a dotted expression. - */ -function getFirstQualifier(node: ts.Node): ts.Identifier|null { - switch (node.kind) { - case ts.SyntaxKind.PropertyAccessExpression: { - // For expression position - let lhs = node; - do { - lhs = (lhs).expression; - } while (lhs && lhs.kind !== ts.SyntaxKind.Identifier); - - return lhs; - } - case ts.SyntaxKind.TypeReference: { - // For type position - let lhs: ts.Node = (node).typeName; - do { - lhs = (lhs).left; - } while (lhs && lhs.kind !== ts.SyntaxKind.Identifier); - - return lhs; - } - default: - return null; - } -} - -function createErrorMessage(node: ts.Node, message: string): string { - const sourceFile = node.getSourceFile(); - let position; - if (sourceFile) { - const {line, character} = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - position = `${sourceFile.fileName}(${line + 1},${character + 1})`; - } else { - position = ''; - } - - return `${position}: error: ${message}`; -} - -function hasModifier(node: ts.Node, modifierKind: ts.SyntaxKind): boolean { - return !!node.modifiers && node.modifiers.some(x => x.kind === modifierKind); -} - -function applyDefaultTagOptions(tagOptions: JsDocTagOptions|undefined): JsDocTagOptions { - return {requireAtLeastOne: [], banned: [], toCopy: [], ...tagOptions}; -} - -function getName(node: any) { - return '`' + (node.name && node.name.text ? node.name.text : node.getText()) + '`'; -} diff --git a/tools/ts-api-guardian/package.json b/tools/ts-api-guardian/package.json deleted file mode 100644 index 0a4d56e73d..0000000000 --- a/tools/ts-api-guardian/package.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "name": "ts-api-guardian", - "version": "0.6.0", - "description": "Guards the API of TypeScript libraries!", - "main": "lib/main.js", - "typings": "lib/main.d.ts", - "bin": { - "ts-api-guardian": "./bin/ts-api-guardian" - }, - "directories": { - "test": "test" - }, - "peerDependencies": { - "typescript": "~3.9.2" - }, - "dependencies": { - "chalk": "^4.0.0", - "diff": "^5.0.0", - "minimist": "^1.2.0" - }, - "devDependencies": { - "@types/diff": "^5.0.0", - "@types/jasmine": "^3.0.0", - "@types/minimist": "^1.2.0", - "@types/node": "^10.9.4", - "jasmine": "^3.1.0", - "source-map-support": "^0.5.9", - "typescript": "4.3.4" - }, - "keywords": [ - "typescript" - ], - "contributors": [ - "Alan Agius (https://github.com/alan-agius4/)", - "Alex Eagle (https://angular.io/)", - "Martin Probst (https://angular.io/)", - "Victor Savkin (https://victorsavkin.com)", - "Igor Minar (https://angular.io/)" - ], - "license": "MIT", - "bugs": { - "url": "https://github.com/angular/angular/issues" - }, - "homepage": "https://github.com/angular/angular/tools/ts-api-guardian", - "repository": { - "type": "git", - "url": "https://github.com/angular/angular.git", - "directory": "tools/ts-api-guardian" - }, - "publishConfig": { - "registry": "https://wombat-dressing-room.appspot.com" - } -} diff --git a/tools/ts-api-guardian/test/bootstrap.ts b/tools/ts-api-guardian/test/bootstrap.ts deleted file mode 100644 index b67b2fb564..0000000000 --- a/tools/ts-api-guardian/test/bootstrap.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 - */ - -const path = require('path'); -const {runfiles} = require('@bazel/runfiles'); - -// Change directories to the path of the ts-api-guardian source tree. We need to resolve an actual -// path of a tree because we want to determine the path to the directory that includes all -// test fixture runfiles. On Windows this is usually the original non-sandboxed disk location, -// otherwise this just refers to the runfile directory with all the proper symlinked files. -// NB: we resolve `test/fixtures/empty.ts` and then step up 3 folders so to ensure we resolve to the -// root of the source tree and not the output tree on Windows where there are no runfiles. -// TODO: remove the whole bootstrap file once the tests are Bazel and Windows compatible. -process.chdir(path.resolve( - runfiles.resolve('angular/tools/ts-api-guardian/test/fixtures/empty.ts'), '../../..')); diff --git a/tools/ts-api-guardian/test/cli_e2e_test.ts b/tools/ts-api-guardian/test/cli_e2e_test.ts deleted file mode 100644 index 3b5ad0b91b..0000000000 --- a/tools/ts-api-guardian/test/cli_e2e_test.ts +++ /dev/null @@ -1,148 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 child_process from 'child_process'; -import * as fs from 'fs'; -import * as path from 'path'; -import {assertFileEqual} from './helpers'; - -const BINARY_PATH = require.resolve('../ts-api-guardian/bin/ts-api-guardian'); - -describe('cli: e2e test', () => { - const outDir = path.join(process.env['TEST_TMPDIR'], 'tmp'); - - beforeEach(() => { - if (!fs.existsSync(outDir)) { - fs.mkdirSync(outDir); - } - }); - - afterEach(() => { - fs.rmdirSync(outDir, {recursive: true}); - }); - - it('should print usage without any argument', () => { - const {stderr} = execute([]); - expect(stderr).toMatch(/Usage/); - }); - - it('should show help message with --help', () => { - const {stdout} = execute(['--help']); - expect(stdout).toMatch(/Usage/); - }); - - it('should generate golden file with --out', () => { - const simpleFile = path.join(outDir, 'simple.d.ts'); - const {status, stderr} = execute(['--out', simpleFile, 'test/fixtures/simple.d.ts']); - expect(status).toBe(0, stderr); - assertFileEqual(simpleFile, 'test/fixtures/simple_expected.d.ts'); - }); - - it('should verify golden file with --verify and exit cleanly on no difference', () => { - const {stdout, status} = - execute(['--verify', 'test/fixtures/simple_expected.d.ts', 'test/fixtures/simple.d.ts']); - expect(stdout).toBe(''); - expect(status).toBe(0); - }); - - it('should verify golden file with --verify and exit with error on difference', () => { - const {stdout, status} = execute( - ['--verify', 'test/fixtures/verify_expected.d.ts', 'test/fixtures/verify_entrypoint.d.ts']); - expect(stdout).toBe(fs.readFileSync('test/fixtures/verify.patch').toString()); - expect(status).toBe(1); - }); - - it('should generate multiple golden files with --outDir and --rootDir', () => { - const {status} = execute([ - '--outDir', outDir, '--rootDir', 'test/fixtures', 'test/fixtures/simple.d.ts', - 'test/fixtures/sorting.d.ts' - ]); - expect(status).toBe(0); - assertFileEqual(path.join(outDir, 'simple.d.ts'), 'test/fixtures/simple_expected.d.ts'); - assertFileEqual(path.join(outDir, 'sorting.d.ts'), 'test/fixtures/sorting_expected.d.ts'); - }); - - it('should verify multiple golden files with --verifyDir and --rootDir', () => { - copyFile('test/fixtures/simple_expected.d.ts', path.join(outDir, 'simple.d.ts')); - copyFile('test/fixtures/sorting_expected.d.ts', path.join(outDir, 'sorting.d.ts')); - const {stdout, status} = execute([ - '--verifyDir', outDir, '--rootDir', 'test/fixtures', 'test/fixtures/simple.d.ts', - 'test/fixtures/sorting.d.ts' - ]); - expect(stdout).toBe(''); - expect(status).toBe(0); - }); - - it('should generate respecting --stripExportPattern', () => { - const {status} = execute([ - '--out', path.join(outDir, 'underscored.d.ts'), '--stripExportPattern', '^__.*', - 'test/fixtures/underscored.d.ts' - ]); - - expect(status).toBe(0); - assertFileEqual( - path.join(outDir, 'underscored.d.ts'), 'test/fixtures/underscored_expected.d.ts'); - }); - - it('should not throw for aliased stripped exports', () => { - const {status} = execute([ - '--out', path.join(outDir, 'stripped_alias.d.ts'), '--stripExportPattern', '^__.*', - 'test/fixtures/stripped_alias.d.ts' - ]); - expect(status).toBe(0); - assertFileEqual( - path.join(outDir, 'stripped_alias.d.ts'), 'test/fixtures/stripped_alias_expected.d.ts'); - }); - - it('should verify respecting --stripExportPattern', () => { - const {stdout, status} = execute([ - '--verify', 'test/fixtures/underscored_expected.d.ts', 'test/fixtures/underscored.d.ts', - '--stripExportPattern', '^__.*' - ]); - expect(stdout).toBe(''); - expect(status).toBe(0); - }); - - it('should respect --allowModuleIdentifiers', () => { - const {stdout, status} = execute([ - '--verify', 'test/fixtures/module_identifier_expected.d.ts', '--allowModuleIdentifiers', - 'foo', 'test/fixtures/module_identifier.d.ts' - ]); - expect(stdout).toBe(''); - expect(status).toBe(0); - }); -}); - -function copyFile(sourceFile: string, targetFile: string) { - fs.writeFileSync(targetFile, fs.readFileSync(sourceFile)); -} - -function execute(args: string[]): {stdout: string, stderr: string, status: number} { - // We need to determine the directory that includes the `ts-api-guardian` npm_package that - // will be used to spawn the CLI binary. This is a workaround because technically we shouldn't - // spawn a child process that doesn't have the custom NodeJS module resolution for Bazel. - const nodePath = [ - path.join(require.resolve('npm/node_modules/chalk/package.json'), '../../'), - path.join(require.resolve('../lib/cli.js'), '../../'), - ].join(process.platform === 'win32' ? ';' : ':'); - - const output = child_process.spawnSync(process.execPath, [BINARY_PATH, ...args], { - env: { - 'NODE_PATH': nodePath, - } - }); - - expect(output.error).toBeFalsy(`Child process failed or timed out: ${output.error}`); - expect(output.signal).toBeFalsy(`Child process killed by signal ${output.signal}`); - - return { - stdout: output.stdout.toString(), - stderr: output.stderr.toString(), - status: output.status - }; -} diff --git a/tools/ts-api-guardian/test/cli_unit_test.ts b/tools/ts-api-guardian/test/cli_unit_test.ts deleted file mode 100644 index d1eff0f96c..0000000000 --- a/tools/ts-api-guardian/test/cli_unit_test.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {parseArguments} from '../lib/cli'; - -describe('cli: parseArguments', () => { - it('should show usage with error when supplied with no arguments', () => { - const {mode, errors} = parseArguments([]); - expect(mode).toBe('help'); - expect(errors).toEqual(['No input file specified.']); - }); - - it('should show usage without error when supplied with --help', () => { - const {mode, errors} = parseArguments(['--help']); - expect(mode).toBe('help'); - expect(errors).toEqual([]); - }); - - it('should show usage with error when supplied with none of --out/verify[Dir]', () => { - const {mode, errors} = parseArguments(['input.d.ts']); - expect(mode).toBe('help'); - expect(errors).toEqual(['Specify either --out[Dir] or --verify[Dir]']); - }); - - it('should show usage with error when supplied with both of --out/verify[Dir]', () => { - const {mode, errors} = - parseArguments(['--out', 'out.d.ts', '--verifyDir', 'golden.d.ts', 'input.d.ts']); - expect(mode).toBe('help'); - expect(errors).toEqual(['Specify either --out[Dir] or --verify[Dir]']); - }); - - it('should show usage with error when supplied without input file', () => { - const {mode, errors} = parseArguments(['--out', 'output.d.ts']); - expect(mode).toBe('help'); - expect(errors).toEqual(['No input file specified.']); - }); - - it('should show usage with error when supplied without input file', () => { - const {mode, errors} = parseArguments(['--out', 'output.d.ts', 'first.d.ts', 'second.d.ts']); - expect(mode).toBe('help'); - expect(errors).toEqual(['More than one input specified. Use --outDir instead.']); - }); - - it('should use out mode when supplied with --out', () => { - const {argv, mode, errors} = parseArguments(['--out', 'out.d.ts', 'input.d.ts']); - expect(argv['out']).toBe('out.d.ts'); - expect(argv._).toEqual(['input.d.ts']); - expect(mode).toBe('out'); - expect(errors).toEqual([]); - }); - - it('should use verify mode when supplied with --verify', () => { - const {argv, mode, errors} = parseArguments(['--verify', 'out.d.ts', 'input.d.ts']); - expect(argv['verify']).toBe('out.d.ts'); - expect(argv._).toEqual(['input.d.ts']); - expect(mode).toBe('verify'); - expect(errors).toEqual([]); - }); - - it('should show usage with error when supplied with --autoDiscoverEntrypoints without --baseDir', - () => { - const {mode, errors} = - parseArguments(['--autoDiscoverEntrypoints', '--outDir', 'something']); - expect(mode).toBe('help'); - expect(errors).toEqual(['--rootDir must be provided with --autoDiscoverEntrypoints.']); - }); - - it('should show usage with error when supplied with --autoDiscoverEntrypoints without --outDir/verifyDir', - () => { - const {mode, errors} = - parseArguments(['--autoDiscoverEntrypoints', '--rootDir', 'something']); - expect(mode).toBe('help'); - expect(errors).toEqual( - ['--outDir or --verifyDir must be used with --autoDiscoverEntrypoints.']); - }); -}); diff --git a/tools/ts-api-guardian/test/fixtures/classes_and_interfaces.d.ts b/tools/ts-api-guardian/test/fixtures/classes_and_interfaces.d.ts deleted file mode 100644 index 485e4ef31a..0000000000 --- a/tools/ts-api-guardian/test/fixtures/classes_and_interfaces.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -export declare class A { - field: string; - method(a: string): number; -} -export interface B { - field: A; -} -export declare class C { - private privateProp; - propWithDefault: number; - protected protectedProp: number; - someProp: string; - constructor(someProp: string, propWithDefault: number, privateProp: any, protectedProp: number); -} diff --git a/tools/ts-api-guardian/test/fixtures/classes_and_interfaces_expected.d.ts b/tools/ts-api-guardian/test/fixtures/classes_and_interfaces_expected.d.ts deleted file mode 100644 index 6c37af1a01..0000000000 --- a/tools/ts-api-guardian/test/fixtures/classes_and_interfaces_expected.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -export declare class A { - field: string; - method(a: string): number; -} - -export interface B { - field: A; -} - -export declare class C { - propWithDefault: number; - protected protectedProp: number; - someProp: string; - constructor(someProp: string, propWithDefault: number, privateProp: any, protectedProp: number); -} diff --git a/tools/ts-api-guardian/test/fixtures/empty.d.ts b/tools/ts-api-guardian/test/fixtures/empty.d.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tools/ts-api-guardian/test/fixtures/empty.ts b/tools/ts-api-guardian/test/fixtures/empty.ts deleted file mode 100644 index 823e9bf40a..0000000000 --- a/tools/ts-api-guardian/test/fixtures/empty.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 - */ diff --git a/tools/ts-api-guardian/test/fixtures/empty_expected.d.ts b/tools/ts-api-guardian/test/fixtures/empty_expected.d.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tools/ts-api-guardian/test/fixtures/enum_as_type.d.ts b/tools/ts-api-guardian/test/fixtures/enum_as_type.d.ts deleted file mode 100644 index db4a3c65e7..0000000000 --- a/tools/ts-api-guardian/test/fixtures/enum_as_type.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -export declare enum Foo { - Alpha = 0, - Beta = 1, -} - -export interface Bar { - field: Foo.Alpha, -} \ No newline at end of file diff --git a/tools/ts-api-guardian/test/fixtures/enum_as_type_expected.d.ts b/tools/ts-api-guardian/test/fixtures/enum_as_type_expected.d.ts deleted file mode 100644 index 63e8c6ba32..0000000000 --- a/tools/ts-api-guardian/test/fixtures/enum_as_type_expected.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface Bar { - field: Foo.Alpha, -} - -export declare enum Foo { - Alpha = 0, - Beta = 1, -} diff --git a/tools/ts-api-guardian/test/fixtures/exports_type_and_value.d.ts b/tools/ts-api-guardian/test/fixtures/exports_type_and_value.d.ts deleted file mode 100644 index 4e161fe44c..0000000000 --- a/tools/ts-api-guardian/test/fixtures/exports_type_and_value.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface TypeOnly { field: string; } -export interface TypeAndValue { field: string; } - -export const TypeAndValue: Function; \ No newline at end of file diff --git a/tools/ts-api-guardian/test/fixtures/exports_type_and_value_expected.d.ts b/tools/ts-api-guardian/test/fixtures/exports_type_and_value_expected.d.ts deleted file mode 100644 index 63ff608813..0000000000 --- a/tools/ts-api-guardian/test/fixtures/exports_type_and_value_expected.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface TypeAndValue { field: string; } - -export const TypeAndValue: Function; - -export interface TypeOnly { field: string; } diff --git a/tools/ts-api-guardian/test/fixtures/keyof.d.ts b/tools/ts-api-guardian/test/fixtures/keyof.d.ts deleted file mode 100644 index c2b6c1749c..0000000000 --- a/tools/ts-api-guardian/test/fixtures/keyof.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export declare type SimpleChanges = { - [P in keyof T]?: any; -}; \ No newline at end of file diff --git a/tools/ts-api-guardian/test/fixtures/keyof_expected.d.ts b/tools/ts-api-guardian/test/fixtures/keyof_expected.d.ts deleted file mode 100644 index b044702368..0000000000 --- a/tools/ts-api-guardian/test/fixtures/keyof_expected.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export declare type SimpleChanges = { - [P in keyof T]?: any; -}; diff --git a/tools/ts-api-guardian/test/fixtures/module_identifier.d.ts b/tools/ts-api-guardian/test/fixtures/module_identifier.d.ts deleted file mode 100644 index f8af6eedf4..0000000000 --- a/tools/ts-api-guardian/test/fixtures/module_identifier.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as foo from './somewhere'; - -/** @publicApi */ -export declare class A extends foo.Bar { -} diff --git a/tools/ts-api-guardian/test/fixtures/module_identifier_expected.d.ts b/tools/ts-api-guardian/test/fixtures/module_identifier_expected.d.ts deleted file mode 100644 index 174b833508..0000000000 --- a/tools/ts-api-guardian/test/fixtures/module_identifier_expected.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export declare class A extends foo.Bar { -} diff --git a/tools/ts-api-guardian/test/fixtures/reexported.d.ts b/tools/ts-api-guardian/test/fixtures/reexported.d.ts deleted file mode 100644 index b583f44e4f..0000000000 --- a/tools/ts-api-guardian/test/fixtures/reexported.d.ts +++ /dev/null @@ -1 +0,0 @@ -export { A, B } from './simple'; diff --git a/tools/ts-api-guardian/test/fixtures/reexported_aliased.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_aliased.d.ts deleted file mode 100644 index 058677bf1b..0000000000 --- a/tools/ts-api-guardian/test/fixtures/reexported_aliased.d.ts +++ /dev/null @@ -1 +0,0 @@ -export { A as Apple } from './classes_and_interfaces'; diff --git a/tools/ts-api-guardian/test/fixtures/reexported_classes.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_classes.d.ts deleted file mode 100644 index 01a7a03a10..0000000000 --- a/tools/ts-api-guardian/test/fixtures/reexported_classes.d.ts +++ /dev/null @@ -1 +0,0 @@ -export { A } from './classes_and_interfaces'; diff --git a/tools/ts-api-guardian/test/fixtures/reexported_classes_expected.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_classes_expected.d.ts deleted file mode 100644 index b8c5750924..0000000000 --- a/tools/ts-api-guardian/test/fixtures/reexported_classes_expected.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export declare class A { - field: string; - method(a: string): number; -} diff --git a/tools/ts-api-guardian/test/fixtures/reexported_expected.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_expected.d.ts deleted file mode 100644 index 7020ff3c3f..0000000000 --- a/tools/ts-api-guardian/test/fixtures/reexported_expected.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export declare const A: string; - -export declare var B: string; diff --git a/tools/ts-api-guardian/test/fixtures/reexported_extern.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_extern.d.ts deleted file mode 100644 index 0c0d859899..0000000000 --- a/tools/ts-api-guardian/test/fixtures/reexported_extern.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * We want to ensure that external modules are not resolved. Typescript happens - * to be conveniently available in our environment. - */ -export { CompilerHost } from 'typescript'; diff --git a/tools/ts-api-guardian/test/fixtures/reexported_extern_expected.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_extern_expected.d.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tools/ts-api-guardian/test/fixtures/reexported_star.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_star.d.ts deleted file mode 100644 index 882a36dbe8..0000000000 --- a/tools/ts-api-guardian/test/fixtures/reexported_star.d.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './simple'; diff --git a/tools/ts-api-guardian/test/fixtures/reexported_star_expected.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_star_expected.d.ts deleted file mode 100644 index 7020ff3c3f..0000000000 --- a/tools/ts-api-guardian/test/fixtures/reexported_star_expected.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export declare const A: string; - -export declare var B: string; diff --git a/tools/ts-api-guardian/test/fixtures/simple.d.ts b/tools/ts-api-guardian/test/fixtures/simple.d.ts deleted file mode 100644 index b3c07b7e77..0000000000 --- a/tools/ts-api-guardian/test/fixtures/simple.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** @publicApi */ -export declare const A: string; -/** @publicApi */ -export declare var B: string; diff --git a/tools/ts-api-guardian/test/fixtures/simple_expected.d.ts b/tools/ts-api-guardian/test/fixtures/simple_expected.d.ts deleted file mode 100644 index 7020ff3c3f..0000000000 --- a/tools/ts-api-guardian/test/fixtures/simple_expected.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export declare const A: string; - -export declare var B: string; diff --git a/tools/ts-api-guardian/test/fixtures/sorting.d.ts b/tools/ts-api-guardian/test/fixtures/sorting.d.ts deleted file mode 100644 index 59daef5b7b..0000000000 --- a/tools/ts-api-guardian/test/fixtures/sorting.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** @publicApi */ -export declare type E = string; -/** @publicApi */ -export interface D { - e: number; -} -/** @publicApi */ -export declare var e: C; -/** @publicApi */ -export declare class C { - e: number; - d: string; -} -/** @publicApi */ -export declare function b(): boolean; -/** @publicApi */ -export declare const a: string; diff --git a/tools/ts-api-guardian/test/fixtures/sorting_expected.d.ts b/tools/ts-api-guardian/test/fixtures/sorting_expected.d.ts deleted file mode 100644 index d3921f8d91..0000000000 --- a/tools/ts-api-guardian/test/fixtures/sorting_expected.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -export declare const a: string; - -export declare function b(): boolean; - -export declare class C { - d: string; - e: number; -} - -export interface D { - e: number; -} - -export declare var e: C; - -export declare type E = string; diff --git a/tools/ts-api-guardian/test/fixtures/stripped_alias.d.ts b/tools/ts-api-guardian/test/fixtures/stripped_alias.d.ts deleted file mode 100644 index b3ba8b7a40..0000000000 --- a/tools/ts-api-guardian/test/fixtures/stripped_alias.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export {original_symbol as __private_symbol} from './stripped_alias_original'; -/** @publicApi */ -export class B { -} \ No newline at end of file diff --git a/tools/ts-api-guardian/test/fixtures/stripped_alias_expected.d.ts b/tools/ts-api-guardian/test/fixtures/stripped_alias_expected.d.ts deleted file mode 100644 index 94fa3c9c47..0000000000 --- a/tools/ts-api-guardian/test/fixtures/stripped_alias_expected.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export class B { -} diff --git a/tools/ts-api-guardian/test/fixtures/stripped_alias_original.d.ts b/tools/ts-api-guardian/test/fixtures/stripped_alias_original.d.ts deleted file mode 100644 index 76536ae835..0000000000 --- a/tools/ts-api-guardian/test/fixtures/stripped_alias_original.d.ts +++ /dev/null @@ -1 +0,0 @@ -export let original_symbol: number; \ No newline at end of file diff --git a/tools/ts-api-guardian/test/fixtures/type_literals.d.ts b/tools/ts-api-guardian/test/fixtures/type_literals.d.ts deleted file mode 100644 index 1a2e41b591..0000000000 --- a/tools/ts-api-guardian/test/fixtures/type_literals.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class UsesTypeLiterals { - a: number | undefined; - b: number | null; - c: number | true; - d: number | null | undefined; - e: Array; -} \ No newline at end of file diff --git a/tools/ts-api-guardian/test/fixtures/type_literals_expected.d.ts b/tools/ts-api-guardian/test/fixtures/type_literals_expected.d.ts deleted file mode 100644 index ce70fa6fa3..0000000000 --- a/tools/ts-api-guardian/test/fixtures/type_literals_expected.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class UsesTypeLiterals { - a: number | undefined; - b: number | null; - c: number | true; - d: number | null | undefined; - e: Array; -} diff --git a/tools/ts-api-guardian/test/fixtures/underscored.d.ts b/tools/ts-api-guardian/test/fixtures/underscored.d.ts deleted file mode 100644 index 8ff7249036..0000000000 --- a/tools/ts-api-guardian/test/fixtures/underscored.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const __a__: string; -/** @publicApi */ -export class B { -} diff --git a/tools/ts-api-guardian/test/fixtures/underscored_expected.d.ts b/tools/ts-api-guardian/test/fixtures/underscored_expected.d.ts deleted file mode 100644 index 94fa3c9c47..0000000000 --- a/tools/ts-api-guardian/test/fixtures/underscored_expected.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export class B { -} diff --git a/tools/ts-api-guardian/test/fixtures/verify.patch b/tools/ts-api-guardian/test/fixtures/verify.patch deleted file mode 100644 index bb23ff2ee2..0000000000 --- a/tools/ts-api-guardian/test/fixtures/verify.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- test/fixtures/verify_expected.d.ts Golden file -+++ test/fixtures/verify_expected.d.ts Generated API -@@ -1,5 +1,6 @@ - export interface A { - c: number; -- a(arg: any[]): any; -- b: string; -+ a(arg: any[]): {[name: string]: number}; - } -+ -+export declare const b: boolean; diff --git a/tools/ts-api-guardian/test/fixtures/verify_entrypoint.d.ts b/tools/ts-api-guardian/test/fixtures/verify_entrypoint.d.ts deleted file mode 100644 index 6c5d1c5cff..0000000000 --- a/tools/ts-api-guardian/test/fixtures/verify_entrypoint.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** @publicApi */ -export interface A { - c: number; - a(arg: any[]): {[name: string]: number}; -} -export { b } from './verify_submodule'; diff --git a/tools/ts-api-guardian/test/fixtures/verify_expected.d.ts b/tools/ts-api-guardian/test/fixtures/verify_expected.d.ts deleted file mode 100644 index c7d5188ec4..0000000000 --- a/tools/ts-api-guardian/test/fixtures/verify_expected.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface A { - c: number; - a(arg: any[]): any; - b: string; -} diff --git a/tools/ts-api-guardian/test/fixtures/verify_submodule.d.ts b/tools/ts-api-guardian/test/fixtures/verify_submodule.d.ts deleted file mode 100644 index ae7a488d4b..0000000000 --- a/tools/ts-api-guardian/test/fixtures/verify_submodule.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @publicApi */ -export declare const b: boolean; diff --git a/tools/ts-api-guardian/test/helpers.ts b/tools/ts-api-guardian/test/helpers.ts deleted file mode 100644 index 5079a45045..0000000000 --- a/tools/ts-api-guardian/test/helpers.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {readFileSync} from 'fs'; - -export function assertFileEqual(actualFile: string, expectedFile: string) { - expect(readFileSync(actualFile).toString()).toBe(readFileSync(expectedFile).toString()); -} diff --git a/tools/ts-api-guardian/test/integration_test.ts b/tools/ts-api-guardian/test/integration_test.ts deleted file mode 100644 index e46881059b..0000000000 --- a/tools/ts-api-guardian/test/integration_test.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 fs from 'fs'; -import * as path from 'path'; - -import * as main from '../lib/main'; - -import {assertFileEqual} from './helpers'; - -describe('integration test: public api', () => { - let _warn: any = null; - let warnings: string[] = []; - beforeEach(() => { - _warn = console.warn; - console.warn = (...args: string[]) => warnings.push(args.join(' ')); - }); - - afterEach(() => { - console.warn = _warn; - warnings = []; - _warn = null; - }); - - it('should handle empty files', () => { - check('test/fixtures/empty.d.ts', 'test/fixtures/empty_expected.d.ts'); - }); - - it('should include symbols', () => { - check('test/fixtures/simple.d.ts', 'test/fixtures/simple_expected.d.ts'); - }); - - it('should include symbols reexported explicitly', () => { - check('test/fixtures/reexported.d.ts', 'test/fixtures/reexported_expected.d.ts'); - }); - - it('should include symbols reexported with *', () => { - check('test/fixtures/reexported_star.d.ts', 'test/fixtures/reexported_star_expected.d.ts'); - }); - - it('should include members of classes and interfaces', () => { - check( - 'test/fixtures/classes_and_interfaces.d.ts', - 'test/fixtures/classes_and_interfaces_expected.d.ts'); - }); - - it('should include value and type', () => { - check( - 'test/fixtures/exports_type_and_value.d.ts', - 'test/fixtures/exports_type_and_value_expected.d.ts'); - }); - - it('should include members reexported classes', () => { - check( - 'test/fixtures/reexported_classes.d.ts', 'test/fixtures/reexported_classes_expected.d.ts'); - }); - - it('should remove reexported external symbols', () => { - check('test/fixtures/reexported_extern.d.ts', 'test/fixtures/reexported_extern_expected.d.ts'); - expect(warnings).toEqual([ - 'test/fixtures/reexported_extern.d.ts(5,1): error: No export declaration found for symbol "CompilerHost"' - ]); - }); - - it('should support type literals', () => { - check('test/fixtures/type_literals.d.ts', 'test/fixtures/type_literals_expected.d.ts'); - }); - - it('should allow enums as types', () => { - check('test/fixtures/enum_as_type.d.ts', 'test/fixtures/enum_as_type_expected.d.ts'); - }); - - it('should throw on passing a .ts file as an input', () => { - expect(() => main.publicApi('test/fixtures/empty.ts')) - .toThrowError('Source file "test/fixtures/empty.ts" is not a declaration file'); - }); - - it('should respect serialization options', () => { - check( - 'test/fixtures/underscored.d.ts', 'test/fixtures/underscored_expected.d.ts', - {stripExportPattern: /^__.*/}); - }); -}); - -describe('integration test: generateGoldenFile', () => { - 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'); - - beforeEach(() => { - if (!fs.existsSync(outDir)) { - fs.mkdirSync(outDir); - } - }); - - afterEach(() => { - fs.rmdirSync(outDir, {recursive: true}); - }); - - - it('should generate a golden file', () => { - main.generateGoldenFile('test/fixtures/reexported_classes.d.ts', outFile); - assertFileEqual(outFile, 'test/fixtures/reexported_classes_expected.d.ts'); - }); - - it('should generate a golden file with any ancestor directory created', () => { - main.generateGoldenFile('test/fixtures/reexported_classes.d.ts', deepOutFile); - assertFileEqual(deepOutFile, 'test/fixtures/reexported_classes_expected.d.ts'); - }); - - it('should respect serialization options', () => { - main.generateGoldenFile( - 'test/fixtures/underscored.d.ts', outFile, {stripExportPattern: /^__.*/}); - assertFileEqual(outFile, 'test/fixtures/underscored_expected.d.ts'); - }); - - it('should generate a golden file with keyof', () => { - main.generateGoldenFile('test/fixtures/keyof.d.ts', outFile); - assertFileEqual(outFile, 'test/fixtures/keyof_expected.d.ts'); - }); -}); - -describe('integration test: verifyAgainstGoldenFile', () => { - it('should check an entrypoint against a golden file on equal', () => { - const diff = main.verifyAgainstGoldenFile( - 'test/fixtures/reexported_classes.d.ts', 'test/fixtures/reexported_classes_expected.d.ts'); - expect(diff).toBe(''); - }); - - it('should check an entrypoint against a golden file with proper diff message', () => { - const diff = main.verifyAgainstGoldenFile( - 'test/fixtures/verify_entrypoint.d.ts', 'test/fixtures/verify_expected.d.ts'); - expect(diff).toBe(fs.readFileSync('test/fixtures/verify.patch').toString()); - }); - - it('should respect serialization options', () => { - const diff = main.verifyAgainstGoldenFile( - 'test/fixtures/underscored.d.ts', 'test/fixtures/underscored_expected.d.ts', - {stripExportPattern: /^__.*/}); - expect(diff).toBe(''); - }); -}); - -function check(sourceFile: string, expectedFile: string, options: main.SerializationOptions = {}) { - expect(main.publicApi(sourceFile, options)).toBe(fs.readFileSync(expectedFile).toString()); -} diff --git a/tools/ts-api-guardian/test/unit_test.ts b/tools/ts-api-guardian/test/unit_test.ts deleted file mode 100644 index c74205b720..0000000000 --- a/tools/ts-api-guardian/test/unit_test.ts +++ /dev/null @@ -1,655 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 ts from 'typescript'; - -import {publicApiInternal, SerializationOptions} from '../lib/serializer'; - -const classesAndInterfaces = ` - export declare class A { - field: string; - method(a: string): number; - } - export interface B { - field: A; - } - export declare class C { - someProp: string; - propWithDefault: number; - private privateProp; - protected protectedProp: number; - constructor(someProp: string, propWithDefault: number, privateProp: any, protectedProp: number); - } -`; - -describe('unit test', () => { - let _warn: any = null; - let warnings: string[] = []; - beforeEach(() => { - _warn = console.warn; - console.warn = (...args: string[]) => warnings.push(args.join(' ')); - }); - - afterEach(() => { - console.warn = _warn; - warnings = []; - _warn = null; - }); - - it('should ignore private methods', () => { - const input = ` - export declare class A { - fa(): void; - protected fb(): void; - private fc(); - } - `; - const expected = ` - export declare class A { - fa(): void; - protected fb(): void; - } - `; - check({'file.d.ts': input}, expected); - }); - - it('should support overloads functions', () => { - const input = ` - export declare function group(steps: AnimationMetadata[], options?: AnimationOptions | null): AnimationGroupMetadata; - - export declare function registerLocaleData(data: any, extraData?: any): void; - export declare function registerLocaleData(data: any, localeId?: string, extraData?: any): void; - `; - - const expected = ` - export declare function group(steps: AnimationMetadata[], options?: AnimationOptions | null): AnimationGroupMetadata; - - export declare function registerLocaleData(data: any, extraData?: any): void; - export declare function registerLocaleData(data: any, localeId?: string, extraData?: any): void; - `; - - check({'file.d.ts': input}, expected); - }); - - it('should ignore private props', () => { - const input = ` - export declare class A { - fa: any; - protected fb: any; - private fc; - } - `; - const expected = ` - export declare class A { - fa: any; - protected fb: any; - } - `; - check({'file.d.ts': input}, expected); - }); - - it('should support imports without capturing imports', () => { - const input = ` - import {A} from './classes_and_interfaces'; - export declare class C { - field: A; - } - `; - const expected = ` - export declare class C { - field: A; - } - `; - check({'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input}, expected); - }); - - it('should throw on aliased reexports', () => { - const input = ` - 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.'); - }); - - it('should remove reexported external symbols', () => { - const input = ` - export { Foo } from 'some-external-module-that-cannot-be-resolved'; - `; - const expected = ` - `; - check({'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input}, expected); - expect(warnings).toEqual( - ['file.d.ts(1,1): error: No export declaration found for symbol "Foo"']); - }); - - it('should sort exports', () => { - const input = ` - export declare type E = string; - export interface D { - e: number; - } - export declare var e: C; - export declare class C { - e: number; - d: string; - } - export declare function b(): boolean; - export declare const a: string; - `; - const expected = ` - export declare const a: string; - - export declare function b(): boolean; - - export declare class C { - d: string; - e: number; - } - - export interface D { - e: number; - } - - export declare var e: C; - - export declare type E = string; - `; - check({'file.d.ts': input}, expected); - }); - - it('should sort class members', () => { - const input = ` - export class A { - f: number; - static foo(): void; - c: string; - static a: boolean; - constructor(); - static bar(): void; - } - `; - const expected = ` - export class A { - c: string; - f: number; - constructor(); - static a: boolean; - static bar(): void; - static foo(): void; - } - `; - check({'file.d.ts': input}, expected); - }); - - it('should sort interface members', () => { - const input = ` - export interface A { - (): void; - [key: string]: any; - c(): void; - a: number; - new (): Object; - } - `; - const expected = ` - export interface A { - a: number; - (): void; - new (): Object; - [key: string]: any; - c(): void; - } - `; - check({'file.d.ts': input}, expected); - }); - - it('should sort class members including readonly', () => { - const input = ` - export declare class DebugNode { - private _debugContext; - nativeNode: any; - listeners: any[]; - parent: any | null; - constructor(nativeNode: any, parent: DebugNode | null, _debugContext: any); - readonly injector: any; - readonly componentInstance: any; - readonly context: any; - readonly references: { - [key: string]: any; - }; - readonly providerTokens: any[]; - } - `; - const expected = ` - export declare class DebugNode { - readonly componentInstance: any; - readonly context: any; - readonly injector: any; - listeners: any[]; - nativeNode: any; - parent: any | null; - readonly providerTokens: any[]; - readonly references: { - [key: string]: any; - }; - constructor(nativeNode: any, parent: DebugNode | null, _debugContext: any); - } - `; - check({'file.d.ts': input}, expected); - }); - - it('should sort two call signatures', () => { - const input = ` - export interface A { - (b: number): void; - (a: number): void; - } - `; - const expected = ` - export interface A { - (a: number): void; - (b: number): void; - } - `; - check({'file.d.ts': input}, expected); - }); - - it('should sort exports including re-exports', () => { - const submodule = ` - export declare var e: C; - export declare class C { - e: number; - d: string; - } - `; - const input = ` - export * from './submodule'; - export declare type E = string; - export interface D { - e: number; - } - export declare function b(): boolean; - export declare const a: string; - `; - const expected = ` - export declare const a: string; - - export declare function b(): boolean; - - export declare class C { - d: string; - e: number; - } - - export interface D { - e: number; - } - - export declare var e: C; - - export declare type E = string; - `; - check({'submodule.d.ts': submodule, 'file.d.ts': input}, expected); - }); - - it('should remove module comments', () => { - const input = ` - /** - * An amazing module. - * @module - */ - /** - * Foo function. - */ - export declare function foo(): boolean; - export declare const bar: number; - `; - const expected = ` - export declare const bar: number; - - export declare function foo(): boolean; - `; - check({'file.d.ts': input}, expected); - }); - - it('should remove class and field comments', () => { - const input = ` - /** - * Does something really cool. - */ - export declare class A { - /** - * A very interesting getter. - */ - b: string; - /** - * A very useful field. - */ - name: string; - } - `; - const expected = ` - export declare class A { - b: string; - name: string; - } - `; - check({'file.d.ts': input}, expected); - }); - - it('should skip symbols matching specified pattern', () => { - const input = ` - export const __a__: string; - export class B { - } - `; - const expected = ` - export class B { - } - `; - check({'file.d.ts': input}, expected, {stripExportPattern: /^__.*/}); - }); - - it('should throw on using module imports in expression position that were not explicitly allowed', - () => { - const input = ` - import * as foo from './foo'; - export declare class A extends foo.A { - } - `; - checkThrows( - {'file.d.ts': input}, - 'file.d.ts(2,32): error: Module identifier "foo" is not allowed. ' + - 'Remove it from source or allow it via --allowModuleIdentifiers.'); - }); - - it('should throw on using module imports in type position that were not explicitly allowed', - () => { - const input = ` - import * as foo from './foo'; - 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 allow it via --allowModuleIdentifiers.'); - }); - - it('should not throw on using explicitly allowed module imports', () => { - const input = ` - import * as foo from './foo'; - export declare class A extends foo.A { - } - `; - const expected = ` - export declare class A extends foo.A { - } - `; - check({'file.d.ts': input}, expected, {allowModuleIdentifiers: ['foo']}); - }); - - it('should not throw if module imports, that were not explicitly allowed, are not used', () => { - const input = ` - import * as foo from './foo'; - export declare class A { - } - `; - const expected = ` - export declare class A { - } - `; - check({'file.d.ts': input}, expected); - }); - - it('should copy specified jsdoc tags of exports in docstrings', () => { - const input = ` - /** - * @deprecated This is useless now - */ - export declare class A { - } - /** - * @experimental - */ - export declare const b: string; - /** - * @stable - */ - export declare var c: number; - `; - const expected = ` - /** @deprecated */ - export declare class A { - } - - /** @experimental */ - export declare const b: string; - - export declare var c: number; - `; - check({'file.d.ts': input}, expected, {exportTags: {toCopy: ['deprecated', 'experimental']}}); - }); - - it('should copy specified jsdoc tags of fields in docstrings', () => { - const input = ` - /** @otherTag */ - export declare class A { - /** - * @stable - */ - value: number; - /** - * @experimental - * @otherTag - */ - constructor(); - /** - * @deprecated - */ - foo(): void; - } - `; - const expected = ` - export declare class A { - value: number; - /** @experimental */ constructor(); - /** @deprecated */ foo(): void; - } - `; - check({'file.d.ts': input}, expected, {memberTags: {toCopy: ['deprecated', 'experimental']}}); - }); - - it('should copy specified jsdoc tags of parameters in docstrings', () => { - const input = ` - export declare class A { - foo(str: string, /** @deprecated */ value: number): void; - } - `; - const expected = ` - export declare class A { - foo(str: string, /** @deprecated */ value: number): void; - } - `; - check({'file.d.ts': input}, expected, {paramTags: {toCopy: ['deprecated', 'experimental']}}); - }); - - it('should throw on using banned jsdoc tags on exports', () => { - const input = ` - /** - * @stable - */ - export declare class A { - value: number; - } - `; - checkThrows( - {'file.d.ts': input}, - 'file.d.ts(4,1): error: Banned jsdoc tags - "@stable" - were found on `A`.', - {exportTags: {banned: ['stable']}}); - }); - - it('should throw on using banned jsdoc tags on fields', () => { - const input = ` - export declare class A { - /** - * @stable - */ - value: number; - } - `; - checkThrows( - {'file.d.ts': input}, - 'file.d.ts(5,3): error: Banned jsdoc tags - "@stable" - were found on `value`.', - {memberTags: {banned: ['stable']}}); - }); - - it('should throw on using banned jsdoc tags on parameters', () => { - const input = ` - export declare class A { - foo(/** @stable */ param: number): void; - } - `; - checkThrows( - {'file.d.ts': input}, - 'file.d.ts(2,22): error: Banned jsdoc tags - "@stable" - were found on `param`.', - {paramTags: {banned: ['stable']}}); - }); - - it('should throw on missing required jsdoc tags on exports', () => { - const input = ` - /** @experimental */ - export declare class A { - value: number; - } - `; - checkThrows( - {'file.d.ts': input}, - 'file.d.ts(2,1): error: Required jsdoc tags - One of the tags: "@stable" - must exist on `A`.', - {exportTags: {requireAtLeastOne: ['stable']}}); - }); - - it('should throw on missing required jsdoc tags on fields', () => { - const input = ` - /** @experimental */ - export declare class A { - value: number; - } - `; - checkThrows( - {'file.d.ts': input}, - 'file.d.ts(3,3): error: Required jsdoc tags - One of the tags: "@stable" - must exist on `value`.', - {memberTags: {requireAtLeastOne: ['stable']}}); - }); - - it('should throw on missing required jsdoc tags on parameters', () => { - const input = ` - /** @experimental */ - export declare class A { - foo(param: number): void; - } - `; - checkThrows( - {'file.d.ts': input}, - 'file.d.ts(3,7): error: Required jsdoc tags - One of the tags: "@stable" - must exist on `param`.', - {paramTags: {requireAtLeastOne: ['stable']}}); - }); - - it('should require at least one of the requireAtLeastOne tags', () => { - const input = ` - /** @experimental */ - export declare class A { - foo(param: number): void; - } - `; - checkThrows( - {'file.d.ts': input}, - 'file.d.ts(3,7): error: Required jsdoc tags - One of the tags: "@stable", "@foo", "@bar" - must exist on `param`.', - {paramTags: {requireAtLeastOne: ['stable', 'foo', 'bar']}}); - }); - - it('should allow with one of the requireAtLeastOne tags found', () => { - const input = ` - /** - * @foo - * @bar - * @stable - */ - export declare class A { - } - /** - * @foo - */ - export declare const b: string; - /** - * @bar - */ - export declare var c: number; - /** - * @stable - */ - export declare function d(): void; - `; - const expected = ` - export declare class A { - } - - export declare const b: string; - - export declare var c: number; - - export declare function d(): void; - `; - check( - {'file.d.ts': input}, expected, - {exportTags: {requireAtLeastOne: ['stable', 'foo', 'bar']}}); - }); -}); - -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); - }, - writeFile: (name, text, writeByteOrderMark) => {}, - fileExists: (filename) => !!files[filename], - readFile: (filename) => stripExtraIndentation(files[filename]), - getDefaultLibFileName: () => 'lib.ts', - useCaseSensitiveFileNames: () => true, - getCanonicalFileName: (filename) => filename, - getCurrentDirectory: () => './', - getNewLine: () => '\n', - getDirectories: () => [] - }; -} - -function check( - files: {[name: string]: string}, expected: string, options: SerializationOptions = {}) { - const actual = publicApiInternal(getMockHost(files), 'file.d.ts', {}, options); - expect(actual.trim()).toBe(stripExtraIndentation(expected).trim()); -} - -function checkThrows( - files: {[name: string]: string}, error: string, options: SerializationOptions = {}) { - expect(() => publicApiInternal(getMockHost(files), 'file.d.ts', {}, options)).toThrowError(error); -} - -function stripExtraIndentation(text: string) { - let lines = text.split('\n'); - // 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; - // Ignore empty line - return line.length ? Math.min(min, indent) : min; - }, text.length); - - return lines.map(line => line.substr(commonIndent)).join('\n') + '\n'; -} diff --git a/tools/tsconfig.json b/tools/tsconfig.json index 086b316599..3a0358f660 100644 --- a/tools/tsconfig.json +++ b/tools/tsconfig.json @@ -26,7 +26,6 @@ "exclude": [ "testing", "node_modules", - "ts-api-guardian", "typings-test", "public_api_guard", "docs" diff --git a/tslint.json b/tslint.json index a08ecf0e5d..2f812a798a 100644 --- a/tslint.json +++ b/tslint.json @@ -116,7 +116,6 @@ // Ignore test files "./packages/compiler-cli/test/compliance/test_cases/**/*", "./packages/localize/**/test_files/**/*", - "./tools/ts-api-guardian/test/fixtures/**/*", "./tools/public_api_guard/**/*.d.ts", "./modules/benchmarks_external/**/*", // Ignore zone.js directory