From 97c3c6056dcde705febdc788ff45140bda90babf Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 11 Jun 2018 13:06:34 -0700 Subject: [PATCH] fix symlink tag, support arbitrary (ie, directory) links via @link Introduce indirect symlink lookup to specifier deriver Use fileset, move exec vfs path resolution :shakes fist: Apply files symlink relative to dirname Use directory function --- src/compiler/checker.ts | 9 +- src/compiler/moduleSpecifiers.ts | 59 +++++++-- src/compiler/types.ts | 1 + src/harness/compilerRunner.ts | 4 +- src/harness/harness.ts | 26 +++- src/harness/vfs.ts | 5 +- src/services/codefixes/importFixes.ts | 2 +- .../symbolLinkDeclarationEmitModuleNames.js | 116 ++++++++++++++++++ ...mbolLinkDeclarationEmitModuleNames.symbols | 59 +++++++++ ...symbolLinkDeclarationEmitModuleNames.types | 63 ++++++++++ .../symbolLinkDeclarationEmitModuleNames.ts | 25 ++++ 11 files changed, 350 insertions(+), 19 deletions(-) create mode 100644 tests/baselines/reference/symbolLinkDeclarationEmitModuleNames.js create mode 100644 tests/baselines/reference/symbolLinkDeclarationEmitModuleNames.symbols create mode 100644 tests/baselines/reference/symbolLinkDeclarationEmitModuleNames.types create mode 100644 tests/cases/compiler/symbolLinkDeclarationEmitModuleNames.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dccf948e7d8..3f8a22ffa86 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4093,7 +4093,14 @@ namespace ts { } else { const contextFile = getSourceFileOfNode(getOriginalNode(context!.enclosingDeclaration))!; - return `"${file.moduleName || moduleSpecifiers.getModuleSpecifier(compilerOptions, contextFile, contextFile.path, file.path, context!.tracker.moduleResolverHost!)}"`; + return `"${file.moduleName || moduleSpecifiers.getModuleSpecifiers( + symbol, + compilerOptions, + contextFile, + context!.tracker.moduleResolverHost!, + context!.tracker.moduleResolverHost!.getSourceFiles!(), + { importModuleSpecifierPreference: "non-relative" } + )[0]}"`; } } const declaration = symbol.declarations[0]; diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 1e560e8e092..404bd192217 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -15,17 +15,20 @@ namespace ts.moduleSpecifiers { // For each symlink/original for a module, returns a list of ways to import that file. export function getModuleSpecifiers( moduleSymbol: Symbol, - program: Program, + compilerOptions: CompilerOptions, importingSourceFile: SourceFile, host: ModuleSpecifierResolutionHost, + files: ReadonlyArray, preferences: ModuleSpecifierPreferences, ): ReadonlyArray> { const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol); if (ambient) return [[ambient]]; - const compilerOptions = program.getCompilerOptions(); - const info = getInfo(compilerOptions, importingSourceFile, importingSourceFile.fileName, host); - const modulePaths = getAllModulePaths(program, getSourceFileOfNode(moduleSymbol.valueDeclaration)); + const info = getInfo(compilerOptions, importingSourceFile, importingSourceFile.path, host); + if (!files) { + return Debug.fail("Files list must be present to resolve symlinks in specifier resolution"); + } + const modulePaths = getAllModulePaths(files, getSourceFileOfNode(moduleSymbol.valueDeclaration), info.getCanonicalFileName, host); const global = mapDefined(modulePaths, moduleFileName => getGlobalModuleSpecifier(moduleFileName, info, host, compilerOptions)); return global.length ? global.map(g => [g]) : modulePaths.map(moduleFileName => @@ -130,15 +133,57 @@ namespace ts.moduleSpecifiers { return firstDefined(imports, ({ text }) => pathIsRelative(text) ? fileExtensionIs(text, Extension.Js) : undefined) || false; } + function discoverProbableSymlinks(files: ReadonlyArray) { + const symlinks = mapDefined(files, sf => + sf.resolvedModules && firstDefinedIterator(sf.resolvedModules.values(), res => + res && res.originalPath && res.resolvedFileName !== res.originalPath ? [res.resolvedFileName, res.originalPath] : undefined)); + const result = createMap(); + if (symlinks) { + for (const [resolvedPath, originalPath] of symlinks) { + const resolvedParts = getPathComponents(resolvedPath); + const originalParts = getPathComponents(originalPath); + while (resolvedParts[resolvedParts.length - 1] === originalParts[originalParts.length - 1]) { + resolvedParts.pop(); + originalParts.pop(); + } + result.set(getPathFromPathComponents(originalParts), getPathFromPathComponents(resolvedParts)); + } + } + return result; + } + + function getAllModulePathsUsingIndirectSymlinks(files: ReadonlyArray, target: string, getCanonicalFileName: (file: string) => string, host: ModuleSpecifierResolutionHost) { + const links = discoverProbableSymlinks(files); + const paths = arrayFrom(links.keys()); + let options: string[] | undefined; + for (const path of paths) { + const resolved = links.get(path)!; + if (startsWith(target, resolved + "/")) { + const relative = getRelativePathFromDirectory(resolved, target, getCanonicalFileName); + const option = resolvePath(path, relative); + if (!host.fileExists || host.fileExists(option)) { + if (!options) options = []; + options.push(option); + } + } + } + const resolvedtarget = host.getCurrentDirectory ? resolvePath(host.getCurrentDirectory(), target) : target; + if (options) { + options.push(resolvedtarget); // Since these are speculative, we also include the original resolved name as a possibility + return options; + } + return [resolvedtarget]; + } + /** * Looks for a existing imports that use symlinks to this module. * Only if no symlink is available, the real path will be used. */ - function getAllModulePaths(program: Program, { fileName }: SourceFile): ReadonlyArray { - const symlinks = mapDefined(program.getSourceFiles(), sf => + function getAllModulePaths(files: ReadonlyArray, { fileName }: SourceFile, getCanonicalFileName: (file: string) => string, host: ModuleSpecifierResolutionHost): ReadonlyArray { + const symlinks = mapDefined(files, sf => sf.resolvedModules && firstDefinedIterator(sf.resolvedModules.values(), res => res && res.resolvedFileName === fileName ? res.originalPath : undefined)); - return symlinks.length === 0 ? [fileName] : symlinks; + return symlinks.length === 0 ? getAllModulePathsUsingIndirectSymlinks(files, fileName, getCanonicalFileName, host) : symlinks; } function getRelativePathNParents(relativePath: string): number { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 33ae3c05d65..d36516a9d38 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5284,6 +5284,7 @@ namespace ts { useCaseSensitiveFileNames?(): boolean; fileExists?(path: string): boolean; readFile?(path: string): string | undefined; + getSourceFiles?(): ReadonlyArray; // Used for cached resolutions to find symlinks without traversing the fs (again) } /** @deprecated See comment on SymbolWriter */ diff --git a/src/harness/compilerRunner.ts b/src/harness/compilerRunner.ts index b899f3107c5..5d8454b2ef4 100644 --- a/src/harness/compilerRunner.ts +++ b/src/harness/compilerRunner.ts @@ -187,7 +187,9 @@ class CompilerTest { this.otherFiles, this.harnessSettings, /*options*/ tsConfigOptions, - /*currentDirectory*/ this.harnessSettings.currentDirectory); + /*currentDirectory*/ this.harnessSettings.currentDirectory, + testCaseContent.symlinks + ); this.options = this.result.options; } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index f894a377ad7..7d0e2875f5b 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1138,6 +1138,7 @@ namespace Harness { { name: "noImplicitReferences", type: "boolean" }, { name: "currentDirectory", type: "string" }, { name: "symlink", type: "string" }, + { name: "link", type: "string" }, // Emitted js baseline will print full paths for every output file { name: "fullEmitPaths", type: "boolean" } ]; @@ -1209,7 +1210,9 @@ namespace Harness { harnessSettings: TestCaseParser.CompilerSettings, compilerOptions: ts.CompilerOptions, // Current directory is needed for rwcRunner to be able to use currentDirectory defined in json file - currentDirectory: string): compiler.CompilationResult { + currentDirectory: string | undefined, + symlinks?: vfs.FileSet + ): compiler.CompilationResult { const options: ts.CompilerOptions & HarnessOptions = compilerOptions ? ts.cloneCompilerOptions(compilerOptions) : { noResolve: false }; options.target = options.target || ts.ScriptTarget.ES3; options.newLine = options.newLine || ts.NewLineKind.CarriageReturnLineFeed; @@ -1246,6 +1249,9 @@ namespace Harness { const docs = inputFiles.concat(otherFiles).map(documents.TextDocument.fromTestFile); const fs = vfs.createFromFileSystem(IO, !useCaseSensitiveFileNames, { documents: docs, cwd: currentDirectory }); + if (symlinks) { + fs.apply(symlinks); + } const host = new fakes.CompilerHost(fs, options); return compiler.compileFiles(host, programFileNames, options); } @@ -1858,6 +1864,7 @@ namespace Harness { // Regex for parsing options in the format "@Alpha: Value of any sort" const optionRegex = /^[\/]{2}\s*@(\w+)\s*:\s*([^\r\n]*)/gm; // multiple matches on multiple lines + const linkRegex = /^[\/]{2}\s*@link\s*:\s*([^\r\n]*)\s*->\s*([^\r\n]*)/gm; // multiple matches on multiple lines export function extractCompilerSettings(content: string): CompilerSettings { const opts: CompilerSettings = {}; @@ -1875,8 +1882,9 @@ namespace Harness { export interface TestCaseContent { settings: CompilerSettings; testUnitData: TestUnitData[]; - tsConfig: ts.ParsedCommandLine; - tsConfigFileUnitData: TestUnitData; + tsConfig: ts.ParsedCommandLine | undefined; + tsConfigFileUnitData: TestUnitData | undefined; + symlinks?: vfs.FileSet; } /** Given a test file containing // @FileName directives, return an array of named units of code to be added to an existing compiler instance */ @@ -1891,10 +1899,16 @@ namespace Harness { let currentFileOptions: any = {}; let currentFileName: any; let refs: string[] = []; + let symlinks: vfs.FileSet | undefined; for (const line of lines) { - const testMetaData = optionRegex.exec(line); - if (testMetaData) { + let testMetaData: RegExpExecArray | null; + const linkMetaData = linkRegex.exec(line); + if (linkMetaData) { + if (!symlinks) symlinks = {}; + symlinks[linkMetaData[2].trim()] = new vfs.Symlink(linkMetaData[1].trim()); + } + else if (testMetaData = optionRegex.exec(line)) { // Comment line, check for global/file @options and record them optionRegex.lastIndex = 0; const metaDataName = testMetaData[1].toLowerCase(); @@ -1983,7 +1997,7 @@ namespace Harness { break; } } - return { settings, testUnitData, tsConfig, tsConfigFileUnitData }; + return { settings, testUnitData, tsConfig, tsConfigFileUnitData, symlinks }; } } diff --git a/src/harness/vfs.ts b/src/harness/vfs.ts index 34a48f59091..1285e867f9e 100644 --- a/src/harness/vfs.ts +++ b/src/harness/vfs.ts @@ -883,7 +883,7 @@ namespace vfs { if (this.stringComparer(vpath.dirname(path), path) === 0) { throw new TypeError("Roots cannot be symbolic links."); } - this.symlinkSync(entry.symlink, path); + this.symlinkSync(vpath.resolve(dirname, entry.symlink), path); this._applyFileExtendedOptions(path, entry); } else if (entry instanceof Link) { @@ -1050,8 +1050,7 @@ namespace vfs { if (symlink) { for (const link of symlink.split(",").map(link => link.trim())) { fs.mkdirpSync(vpath.dirname(link)); - fs.symlinkSync(document.file, link); - fs.filemeta(link).set("document", document); + fs.symlinkSync(vpath.resolve(fs.cwd(), document.file), link); } } } diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 8d87a707c92..077276670ca 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -237,7 +237,7 @@ namespace ts.codefix { preferences: UserPreferences, ): ReadonlyArray { const choicesForEachExportingModule = flatMap(moduleSymbols, ({ moduleSymbol, importKind }) => { - const modulePathsGroups = moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program, sourceFile, host, preferences); + const modulePathsGroups = moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getCompilerOptions(), sourceFile, host, program.getSourceFiles(), preferences); return modulePathsGroups.map(group => group.map(moduleSpecifier => ({ moduleSpecifier, importKind }))); }); // Sort to keep the shortest paths first, but keep [relativePath, importRelativeToBaseUrl] groups together diff --git a/tests/baselines/reference/symbolLinkDeclarationEmitModuleNames.js b/tests/baselines/reference/symbolLinkDeclarationEmitModuleNames.js new file mode 100644 index 00000000000..59eb99f2495 --- /dev/null +++ b/tests/baselines/reference/symbolLinkDeclarationEmitModuleNames.js @@ -0,0 +1,116 @@ +//// [tests/cases/compiler/symbolLinkDeclarationEmitModuleNames.ts] //// + +//// [application.ts] +import { Constructor } from "@loopback/context"; +export type ControllerClass = Constructor; +//// [usage.ts] +import { ControllerClass } from './application'; +import { BindingKey } from '@loopback/context'; + +export const CONTROLLER_CLASS = BindingKey.create(null as any); // line in question +//// [value-promise.ts] +export type Constructor = (...args: any[]) => T; +//// [bindingkey.ts] +import { Constructor } from "@loopback/context" +export class BindingKey { + readonly __type: T; + static create>(ctor: T) { + return new BindingKey(); + } +} + +//// [index.ts] +export * from "./src/value-promise"; +export * from "./src/bindingkey"; + + +//// [value-promise.js] +"use strict"; +exports.__esModule = true; +//// [bindingkey.js] +"use strict"; +exports.__esModule = true; +var BindingKey = /** @class */ (function () { + function BindingKey() { + } + BindingKey.create = function (ctor) { + return new BindingKey(); + }; + return BindingKey; +}()); +exports.BindingKey = BindingKey; +//// [index.js] +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +exports.__esModule = true; +__export(require("./src/bindingkey")); +//// [application.js] +"use strict"; +exports.__esModule = true; +//// [usage.js] +"use strict"; +exports.__esModule = true; +var context_1 = require("@loopback/context"); +exports.CONTROLLER_CLASS = context_1.BindingKey.create(null); // line in question + + +//// [value-promise.d.ts] +export declare type Constructor = (...args: any[]) => T; +//// [bindingkey.d.ts] +import { Constructor } from "@loopback/context"; +export declare class BindingKey { + readonly __type: T; + static create>(ctor: T): BindingKey; +} +//// [index.d.ts] +export * from "./src/value-promise"; +export * from "./src/bindingkey"; +//// [application.d.ts] +import { Constructor } from "@loopback/context"; +export declare type ControllerClass = Constructor; +//// [usage.d.ts] +import { BindingKey } from '@loopback/context'; +export declare const CONTROLLER_CLASS: BindingKey>; + + +//// [DtsFileErrors] + + +tests/cases/compiler/monorepo/context/src/bindingkey.d.ts(1,29): error TS2307: Cannot find module '@loopback/context'. +tests/cases/compiler/monorepo/core/src/application.d.ts(1,29): error TS2307: Cannot find module '@loopback/context'. +tests/cases/compiler/monorepo/core/src/usage.d.ts(1,28): error TS2307: Cannot find module '@loopback/context'. +tests/cases/compiler/monorepo/core/src/usage.d.ts(2,51): error TS2307: Cannot find module '@loopback/context/src/value-promise'. + + +==== tests/cases/compiler/monorepo/core/src/application.d.ts (1 errors) ==== + import { Constructor } from "@loopback/context"; + ~~~~~~~~~~~~~~~~~~~ +!!! error TS2307: Cannot find module '@loopback/context'. + export declare type ControllerClass = Constructor; + +==== tests/cases/compiler/monorepo/core/src/usage.d.ts (2 errors) ==== + import { BindingKey } from '@loopback/context'; + ~~~~~~~~~~~~~~~~~~~ +!!! error TS2307: Cannot find module '@loopback/context'. + export declare const CONTROLLER_CLASS: BindingKey>; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2307: Cannot find module '@loopback/context/src/value-promise'. + +==== /.src/tests/cases/compiler/monorepo/context/src/value-promise.d.ts (0 errors) ==== + export declare type Constructor = (...args: any[]) => T; + +==== /.src/tests/cases/compiler/monorepo/context/src/bindingkey.d.ts (1 errors) ==== + import { Constructor } from "@loopback/context"; + ~~~~~~~~~~~~~~~~~~~ +!!! error TS2307: Cannot find module '@loopback/context'. + export declare class BindingKey { + readonly __type: T; + static create>(ctor: T): BindingKey; + } + +==== /.src/tests/cases/compiler/monorepo/context/index.d.ts (0 errors) ==== + export * from "./src/value-promise"; + export * from "./src/bindingkey"; + \ No newline at end of file diff --git a/tests/baselines/reference/symbolLinkDeclarationEmitModuleNames.symbols b/tests/baselines/reference/symbolLinkDeclarationEmitModuleNames.symbols new file mode 100644 index 00000000000..15b88cbe0bf --- /dev/null +++ b/tests/baselines/reference/symbolLinkDeclarationEmitModuleNames.symbols @@ -0,0 +1,59 @@ +=== tests/cases/compiler/monorepo/core/src/application.ts === +import { Constructor } from "@loopback/context"; +>Constructor : Symbol(Constructor, Decl(application.ts, 0, 8)) + +export type ControllerClass = Constructor; +>ControllerClass : Symbol(ControllerClass, Decl(application.ts, 0, 48)) +>Constructor : Symbol(Constructor, Decl(application.ts, 0, 8)) + +=== tests/cases/compiler/monorepo/core/src/usage.ts === +import { ControllerClass } from './application'; +>ControllerClass : Symbol(ControllerClass, Decl(usage.ts, 0, 8)) + +import { BindingKey } from '@loopback/context'; +>BindingKey : Symbol(BindingKey, Decl(usage.ts, 1, 8)) + +export const CONTROLLER_CLASS = BindingKey.create(null as any); // line in question +>CONTROLLER_CLASS : Symbol(CONTROLLER_CLASS, Decl(usage.ts, 3, 12)) +>BindingKey.create : Symbol(BindingKey.create, Decl(bindingkey.ts, 2, 21)) +>BindingKey : Symbol(BindingKey, Decl(usage.ts, 1, 8)) +>create : Symbol(BindingKey.create, Decl(bindingkey.ts, 2, 21)) +>ControllerClass : Symbol(ControllerClass, Decl(usage.ts, 0, 8)) + +=== tests/cases/compiler/monorepo/context/src/value-promise.ts === +export type Constructor = (...args: any[]) => T; +>Constructor : Symbol(Constructor, Decl(value-promise.ts, 0, 0)) +>T : Symbol(T, Decl(value-promise.ts, 0, 24)) +>args : Symbol(args, Decl(value-promise.ts, 0, 30)) +>T : Symbol(T, Decl(value-promise.ts, 0, 24)) + +=== tests/cases/compiler/monorepo/context/src/bindingkey.ts === +import { Constructor } from "@loopback/context" +>Constructor : Symbol(Constructor, Decl(bindingkey.ts, 0, 8)) + +export class BindingKey { +>BindingKey : Symbol(BindingKey, Decl(bindingkey.ts, 0, 47)) +>T : Symbol(T, Decl(bindingkey.ts, 1, 24)) + + readonly __type: T; +>__type : Symbol(BindingKey.__type, Decl(bindingkey.ts, 1, 28)) +>T : Symbol(T, Decl(bindingkey.ts, 1, 24)) + + static create>(ctor: T) { +>create : Symbol(BindingKey.create, Decl(bindingkey.ts, 2, 21)) +>T : Symbol(T, Decl(bindingkey.ts, 3, 16)) +>Constructor : Symbol(Constructor, Decl(bindingkey.ts, 0, 8)) +>ctor : Symbol(ctor, Decl(bindingkey.ts, 3, 44)) +>T : Symbol(T, Decl(bindingkey.ts, 3, 16)) + + return new BindingKey(); +>BindingKey : Symbol(BindingKey, Decl(bindingkey.ts, 0, 47)) +>T : Symbol(T, Decl(bindingkey.ts, 3, 16)) + } +} + +=== tests/cases/compiler/monorepo/context/index.ts === +export * from "./src/value-promise"; +No type information for this code.export * from "./src/bindingkey"; +No type information for this code. +No type information for this code. \ No newline at end of file diff --git a/tests/baselines/reference/symbolLinkDeclarationEmitModuleNames.types b/tests/baselines/reference/symbolLinkDeclarationEmitModuleNames.types new file mode 100644 index 00000000000..a83fe0d650c --- /dev/null +++ b/tests/baselines/reference/symbolLinkDeclarationEmitModuleNames.types @@ -0,0 +1,63 @@ +=== tests/cases/compiler/monorepo/core/src/application.ts === +import { Constructor } from "@loopback/context"; +>Constructor : any + +export type ControllerClass = Constructor; +>ControllerClass : Constructor +>Constructor : Constructor + +=== tests/cases/compiler/monorepo/core/src/usage.ts === +import { ControllerClass } from './application'; +>ControllerClass : any + +import { BindingKey } from '@loopback/context'; +>BindingKey : typeof BindingKey + +export const CONTROLLER_CLASS = BindingKey.create(null as any); // line in question +>CONTROLLER_CLASS : BindingKey> +>BindingKey.create(null as any) : BindingKey> +>BindingKey.create : >(ctor: T) => BindingKey +>BindingKey : typeof BindingKey +>create : >(ctor: T) => BindingKey +>ControllerClass : import("tests/cases/compiler/monorepo/context/src/value-promise").Constructor +>null as any : any +>null : null + +=== tests/cases/compiler/monorepo/context/src/value-promise.ts === +export type Constructor = (...args: any[]) => T; +>Constructor : Constructor +>T : T +>args : any[] +>T : T + +=== tests/cases/compiler/monorepo/context/src/bindingkey.ts === +import { Constructor } from "@loopback/context" +>Constructor : any + +export class BindingKey { +>BindingKey : BindingKey +>T : T + + readonly __type: T; +>__type : T +>T : T + + static create>(ctor: T) { +>create : >(ctor: T) => BindingKey +>T : T +>Constructor : Constructor +>ctor : T +>T : T + + return new BindingKey(); +>new BindingKey() : BindingKey +>BindingKey : typeof BindingKey +>T : T + } +} + +=== tests/cases/compiler/monorepo/context/index.ts === +export * from "./src/value-promise"; +No type information for this code.export * from "./src/bindingkey"; +No type information for this code. +No type information for this code. \ No newline at end of file diff --git a/tests/cases/compiler/symbolLinkDeclarationEmitModuleNames.ts b/tests/cases/compiler/symbolLinkDeclarationEmitModuleNames.ts new file mode 100644 index 00000000000..95defd4ecc7 --- /dev/null +++ b/tests/cases/compiler/symbolLinkDeclarationEmitModuleNames.ts @@ -0,0 +1,25 @@ +// @declaration: true +// @filename: monorepo/core/src/application.ts +import { Constructor } from "@loopback/context"; +export type ControllerClass = Constructor; +// @filename: monorepo/core/src/usage.ts +import { ControllerClass } from './application'; +import { BindingKey } from '@loopback/context'; + +export const CONTROLLER_CLASS = BindingKey.create(null as any); // line in question +// @filename: monorepo/context/src/value-promise.ts +export type Constructor = (...args: any[]) => T; +// @filename: monorepo/context/src/bindingkey.ts +import { Constructor } from "@loopback/context" +export class BindingKey { + readonly __type: T; + static create>(ctor: T) { + return new BindingKey(); + } +} + +// @filename: monorepo/context/index.ts +export * from "./src/value-promise"; +export * from "./src/bindingkey"; + +// @link: tests/cases/compiler/monorepo/context -> tests/cases/compiler/monorepo/core/node_modules/@loopback/context \ No newline at end of file