refactor(language-service): [Ivy] remove temporary compiler (#38310)
Now that Ivy compiler has a proper `TemplateTypeChecker` interface (see https://github.com/angular/angular/pull/38105) we no longer need to keep the temporary compiler implementation. The temporary compiler was created to enable testing infrastructure to be developed for the Ivy language service. This commit removes the whole `ivy/compiler` directory and moves two functions `createTypeCheckingProgramStrategy` and `getOrCreateTypeCheckScriptInfo` to the `LanguageService` class. Also re-enable the Ivy LS test since it's no longer blocking development. PR Close #38310
This commit is contained in:
parent
3b9c802dee
commit
cfe424e875
|
@ -7,7 +7,13 @@ ts_library(
|
||||||
srcs = glob(["*.ts"]),
|
srcs = glob(["*.ts"]),
|
||||||
deps = [
|
deps = [
|
||||||
"//packages/compiler-cli",
|
"//packages/compiler-cli",
|
||||||
"//packages/language-service/ivy/compiler",
|
"//packages/compiler-cli/src/ngtsc/core",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/core:api",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/incremental",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/shims",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/typecheck",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
||||||
"@npm//typescript",
|
"@npm//typescript",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
load("//tools:defaults.bzl", "ts_library")
|
|
||||||
|
|
||||||
package(default_visibility = ["//packages/language-service/ivy:__pkg__"])
|
|
||||||
|
|
||||||
ts_library(
|
|
||||||
name = "compiler",
|
|
||||||
srcs = glob(["*.ts"]),
|
|
||||||
deps = [
|
|
||||||
"//packages/compiler-cli",
|
|
||||||
"//packages/compiler-cli/src/ngtsc/core",
|
|
||||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
|
||||||
"//packages/compiler-cli/src/ngtsc/incremental",
|
|
||||||
"//packages/compiler-cli/src/ngtsc/typecheck",
|
|
||||||
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
|
||||||
"@npm//typescript",
|
|
||||||
],
|
|
||||||
)
|
|
|
@ -1,2 +0,0 @@
|
||||||
All files in this directory are temporary. This is created to simulate the final
|
|
||||||
form of the Ivy compiler that supports language service.
|
|
|
@ -1,124 +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 {CompilerOptions} from '@angular/compiler-cli';
|
|
||||||
import {NgCompiler, NgCompilerHost} from '@angular/compiler-cli/src/ngtsc/core';
|
|
||||||
import {absoluteFromSourceFile, AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system';
|
|
||||||
import {PatchedProgramIncrementalBuildStrategy} from '@angular/compiler-cli/src/ngtsc/incremental';
|
|
||||||
import {TypeCheckShimGenerator} from '@angular/compiler-cli/src/ngtsc/typecheck';
|
|
||||||
import {TypeCheckingProgramStrategy, UpdateMode} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
|
|
||||||
import * as ts from 'typescript/lib/tsserverlibrary';
|
|
||||||
|
|
||||||
import {makeCompilerHostFromProject} from './compiler_host';
|
|
||||||
|
|
||||||
interface AnalysisResult {
|
|
||||||
compiler: NgCompiler;
|
|
||||||
program: ts.Program;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Compiler {
|
|
||||||
private tsCompilerHost: ts.CompilerHost;
|
|
||||||
private lastKnownProgram: ts.Program|null = null;
|
|
||||||
private readonly strategy: TypeCheckingProgramStrategy;
|
|
||||||
|
|
||||||
constructor(private readonly project: ts.server.Project, private options: CompilerOptions) {
|
|
||||||
this.tsCompilerHost = makeCompilerHostFromProject(project);
|
|
||||||
this.strategy = createTypeCheckingProgramStrategy(project);
|
|
||||||
// Do not retrieve the program in constructor because project is still in
|
|
||||||
// the process of loading, and not all data members have been initialized.
|
|
||||||
}
|
|
||||||
|
|
||||||
setCompilerOptions(options: CompilerOptions) {
|
|
||||||
this.options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
analyze(): AnalysisResult|undefined {
|
|
||||||
const inputFiles = this.project.getRootFiles();
|
|
||||||
const ngCompilerHost =
|
|
||||||
NgCompilerHost.wrap(this.tsCompilerHost, inputFiles, this.options, this.lastKnownProgram);
|
|
||||||
const program = this.strategy.getProgram();
|
|
||||||
const compiler = new NgCompiler(
|
|
||||||
ngCompilerHost, this.options, program, this.strategy,
|
|
||||||
new PatchedProgramIncrementalBuildStrategy(), this.lastKnownProgram);
|
|
||||||
try {
|
|
||||||
// This is the only way to force the compiler to update the typecheck file
|
|
||||||
// in the program. We have to do try-catch because the compiler immediately
|
|
||||||
// throws if it fails to parse any template in the entire program!
|
|
||||||
const d = compiler.getDiagnostics();
|
|
||||||
if (d.length) {
|
|
||||||
// There could be global compilation errors. It's useful to print them
|
|
||||||
// out in development.
|
|
||||||
console.error(d.map(d => ts.flattenDiagnosticMessageText(d.messageText, '\n')));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to analyze program', e.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.lastKnownProgram = compiler.getNextProgram();
|
|
||||||
return {
|
|
||||||
compiler,
|
|
||||||
program: this.lastKnownProgram,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createTypeCheckingProgramStrategy(project: ts.server.Project):
|
|
||||||
TypeCheckingProgramStrategy {
|
|
||||||
return {
|
|
||||||
supportsInlineOperations: false,
|
|
||||||
shimPathForComponent(component: ts.ClassDeclaration): AbsoluteFsPath {
|
|
||||||
return TypeCheckShimGenerator.shimFor(absoluteFromSourceFile(component.getSourceFile()));
|
|
||||||
},
|
|
||||||
getProgram(): ts.Program {
|
|
||||||
const program = project.getLanguageService().getProgram();
|
|
||||||
if (!program) {
|
|
||||||
throw new Error('Language service does not have a program!');
|
|
||||||
}
|
|
||||||
return program;
|
|
||||||
},
|
|
||||||
updateFiles(contents: Map<AbsoluteFsPath, string>, updateMode: UpdateMode) {
|
|
||||||
if (updateMode !== UpdateMode.Complete) {
|
|
||||||
throw new Error(`Incremental update mode is currently not supported`);
|
|
||||||
}
|
|
||||||
for (const [fileName, newText] of contents) {
|
|
||||||
const scriptInfo = getOrCreateTypeCheckScriptInfo(project, fileName);
|
|
||||||
const snapshot = scriptInfo.getSnapshot();
|
|
||||||
const length = snapshot.getLength();
|
|
||||||
scriptInfo.editContent(0, length, newText);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOrCreateTypeCheckScriptInfo(
|
|
||||||
project: ts.server.Project, tcf: string): ts.server.ScriptInfo {
|
|
||||||
// First check if there is already a ScriptInfo for the tcf
|
|
||||||
const {projectService} = project;
|
|
||||||
let scriptInfo = projectService.getScriptInfo(tcf);
|
|
||||||
if (!scriptInfo) {
|
|
||||||
// ScriptInfo needs to be opened by client to be able to set its user-defined
|
|
||||||
// content. We must also provide file content, otherwise the service will
|
|
||||||
// attempt to fetch the content from disk and fail.
|
|
||||||
scriptInfo = projectService.getOrCreateScriptInfoForNormalizedPath(
|
|
||||||
ts.server.toNormalizedPath(tcf),
|
|
||||||
true, // openedByClient
|
|
||||||
'', // fileContent
|
|
||||||
ts.ScriptKind.TS, // scriptKind
|
|
||||||
);
|
|
||||||
if (!scriptInfo) {
|
|
||||||
throw new Error(`Failed to create script info for ${tcf}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add ScriptInfo to project if it's missing. A ScriptInfo needs to be part of
|
|
||||||
// the project so that it becomes part of the program.
|
|
||||||
if (!project.containsScriptInfo(scriptInfo)) {
|
|
||||||
project.addRoot(scriptInfo);
|
|
||||||
}
|
|
||||||
return scriptInfo;
|
|
||||||
}
|
|
|
@ -1,103 +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/lib/tsserverlibrary';
|
|
||||||
|
|
||||||
export function makeCompilerHostFromProject(project: ts.server.Project): ts.CompilerHost {
|
|
||||||
const compilerHost: ts.CompilerHost = {
|
|
||||||
fileExists(fileName: string): boolean {
|
|
||||||
return project.fileExists(fileName);
|
|
||||||
},
|
|
||||||
readFile(fileName: string): string |
|
|
||||||
undefined {
|
|
||||||
return project.readFile(fileName);
|
|
||||||
},
|
|
||||||
directoryExists(directoryName: string): boolean {
|
|
||||||
return project.directoryExists(directoryName);
|
|
||||||
},
|
|
||||||
getCurrentDirectory(): string {
|
|
||||||
return project.getCurrentDirectory();
|
|
||||||
},
|
|
||||||
getDirectories(path: string): string[] {
|
|
||||||
return project.getDirectories(path);
|
|
||||||
},
|
|
||||||
getSourceFile(
|
|
||||||
fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void,
|
|
||||||
shouldCreateNewSourceFile?: boolean): ts.SourceFile |
|
|
||||||
undefined {
|
|
||||||
const path = project.projectService.toPath(fileName);
|
|
||||||
return project.getSourceFile(path);
|
|
||||||
},
|
|
||||||
getSourceFileByPath(
|
|
||||||
fileName: string, path: ts.Path, languageVersion: ts.ScriptTarget,
|
|
||||||
onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): ts.SourceFile |
|
|
||||||
undefined {
|
|
||||||
return project.getSourceFile(path);
|
|
||||||
},
|
|
||||||
getCancellationToken(): ts.CancellationToken {
|
|
||||||
return {
|
|
||||||
isCancellationRequested() {
|
|
||||||
return project.getCancellationToken().isCancellationRequested();
|
|
||||||
},
|
|
||||||
throwIfCancellationRequested() {
|
|
||||||
if (this.isCancellationRequested()) {
|
|
||||||
throw new ts.OperationCanceledException();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getDefaultLibFileName(options: ts.CompilerOptions): string {
|
|
||||||
return project.getDefaultLibFileName();
|
|
||||||
},
|
|
||||||
writeFile(
|
|
||||||
fileName: string, data: string, writeByteOrderMark: boolean,
|
|
||||||
onError?: (message: string) => void, sourceFiles?: readonly ts.SourceFile[]) {
|
|
||||||
return project.writeFile(fileName, data);
|
|
||||||
},
|
|
||||||
getCanonicalFileName(fileName: string): string {
|
|
||||||
return project.projectService.toCanonicalFileName(fileName);
|
|
||||||
},
|
|
||||||
useCaseSensitiveFileNames(): boolean {
|
|
||||||
return project.useCaseSensitiveFileNames();
|
|
||||||
},
|
|
||||||
getNewLine(): string {
|
|
||||||
return project.getNewLine();
|
|
||||||
},
|
|
||||||
readDirectory(
|
|
||||||
rootDir: string, extensions: readonly string[], excludes: readonly string[]|undefined,
|
|
||||||
includes: readonly string[], depth?: number): string[] {
|
|
||||||
return project.readDirectory(rootDir, extensions, excludes, includes, depth);
|
|
||||||
},
|
|
||||||
resolveModuleNames(
|
|
||||||
moduleNames: string[], containingFile: string, reusedNames: string[]|undefined,
|
|
||||||
redirectedReference: ts.ResolvedProjectReference|undefined, options: ts.CompilerOptions):
|
|
||||||
(ts.ResolvedModule | undefined)[] {
|
|
||||||
return project.resolveModuleNames(
|
|
||||||
moduleNames, containingFile, reusedNames, redirectedReference);
|
|
||||||
},
|
|
||||||
resolveTypeReferenceDirectives(
|
|
||||||
typeReferenceDirectiveNames: string[], containingFile: string,
|
|
||||||
redirectedReference: ts.ResolvedProjectReference|undefined, options: ts.CompilerOptions):
|
|
||||||
(ts.ResolvedTypeReferenceDirective | undefined)[] {
|
|
||||||
return project.resolveTypeReferenceDirectives(
|
|
||||||
typeReferenceDirectiveNames, containingFile, redirectedReference);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (project.trace) {
|
|
||||||
compilerHost.trace = function trace(s: string) {
|
|
||||||
project.trace!(s);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (project.realpath) {
|
|
||||||
compilerHost.realpath = function realpath(path: string): string {
|
|
||||||
return project.realpath!(path);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return compilerHost;
|
|
||||||
}
|
|
|
@ -7,30 +7,53 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CompilerOptions, createNgCompilerOptions} from '@angular/compiler-cli';
|
import {CompilerOptions, createNgCompilerOptions} from '@angular/compiler-cli';
|
||||||
|
import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core';
|
||||||
|
import {NgCompilerAdapter} from '@angular/compiler-cli/src/ngtsc/core/api';
|
||||||
|
import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
|
import {PatchedProgramIncrementalBuildStrategy} from '@angular/compiler-cli/src/ngtsc/incremental';
|
||||||
|
import {isShim} from '@angular/compiler-cli/src/ngtsc/shims';
|
||||||
|
import {TypeCheckShimGenerator} from '@angular/compiler-cli/src/ngtsc/typecheck';
|
||||||
|
import {OptimizeFor, TypeCheckingProgramStrategy} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
|
||||||
import * as ts from 'typescript/lib/tsserverlibrary';
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
import {Compiler} from './compiler/compiler';
|
|
||||||
|
|
||||||
export class LanguageService {
|
export class LanguageService {
|
||||||
private options: CompilerOptions;
|
private options: CompilerOptions;
|
||||||
private readonly compiler: Compiler;
|
private lastKnownProgram: ts.Program|null = null;
|
||||||
|
private readonly strategy: TypeCheckingProgramStrategy;
|
||||||
|
private readonly adapter: NgCompilerAdapter;
|
||||||
|
|
||||||
constructor(project: ts.server.Project, private readonly tsLS: ts.LanguageService) {
|
constructor(project: ts.server.Project, private readonly tsLS: ts.LanguageService) {
|
||||||
this.options = parseNgCompilerOptions(project);
|
this.options = parseNgCompilerOptions(project);
|
||||||
|
this.strategy = createTypeCheckingProgramStrategy(project);
|
||||||
|
this.adapter = createNgCompilerAdapter(project);
|
||||||
this.watchConfigFile(project);
|
this.watchConfigFile(project);
|
||||||
this.compiler = new Compiler(project, this.options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getSemanticDiagnostics(fileName: string): ts.Diagnostic[] {
|
getSemanticDiagnostics(fileName: string): ts.Diagnostic[] {
|
||||||
const result = this.compiler.analyze();
|
const program = this.strategy.getProgram();
|
||||||
if (!result) {
|
const compiler = this.createCompiler(program);
|
||||||
return [];
|
if (fileName.endsWith('.ts')) {
|
||||||
}
|
|
||||||
const {compiler, program} = result;
|
|
||||||
const sourceFile = program.getSourceFile(fileName);
|
const sourceFile = program.getSourceFile(fileName);
|
||||||
if (!sourceFile) {
|
if (!sourceFile) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return compiler.getDiagnostics(sourceFile);
|
const ttc = compiler.getTemplateTypeChecker();
|
||||||
|
const diagnostics = ttc.getDiagnosticsForFile(sourceFile, OptimizeFor.SingleFile);
|
||||||
|
this.lastKnownProgram = compiler.getNextProgram();
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
throw new Error('Ivy LS currently does not support external template');
|
||||||
|
}
|
||||||
|
|
||||||
|
private createCompiler(program: ts.Program): NgCompiler {
|
||||||
|
return new NgCompiler(
|
||||||
|
this.adapter,
|
||||||
|
this.options,
|
||||||
|
program,
|
||||||
|
this.strategy,
|
||||||
|
new PatchedProgramIncrementalBuildStrategy(),
|
||||||
|
this.lastKnownProgram,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private watchConfigFile(project: ts.server.Project) {
|
private watchConfigFile(project: ts.server.Project) {
|
||||||
|
@ -47,7 +70,6 @@ export class LanguageService {
|
||||||
project.log(`Config file changed: ${fileName}`);
|
project.log(`Config file changed: ${fileName}`);
|
||||||
if (eventKind === ts.FileWatcherEventKind.Changed) {
|
if (eventKind === ts.FileWatcherEventKind.Changed) {
|
||||||
this.options = parseNgCompilerOptions(project);
|
this.options = parseNgCompilerOptions(project);
|
||||||
this.compiler.setCompilerOptions(this.options);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -66,3 +88,80 @@ export function parseNgCompilerOptions(project: ts.server.Project): CompilerOpti
|
||||||
const basePath = project.getCurrentDirectory();
|
const basePath = project.getCurrentDirectory();
|
||||||
return createNgCompilerOptions(basePath, config, project.getCompilationSettings());
|
return createNgCompilerOptions(basePath, config, project.getCompilationSettings());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createNgCompilerAdapter(project: ts.server.Project): NgCompilerAdapter {
|
||||||
|
return {
|
||||||
|
entryPoint: null, // entry point is only needed if code is emitted
|
||||||
|
constructionDiagnostics: [],
|
||||||
|
ignoreForEmit: new Set(),
|
||||||
|
factoryTracker: null, // no .ngfactory shims
|
||||||
|
unifiedModulesHost: null, // only used in Bazel
|
||||||
|
rootDirs: project.getCompilationSettings().rootDirs?.map(absoluteFrom) || [],
|
||||||
|
isShim,
|
||||||
|
fileExists(fileName: string): boolean {
|
||||||
|
return project.fileExists(fileName);
|
||||||
|
},
|
||||||
|
readFile(fileName: string): string |
|
||||||
|
undefined {
|
||||||
|
return project.readFile(fileName);
|
||||||
|
},
|
||||||
|
getCurrentDirectory(): string {
|
||||||
|
return project.getCurrentDirectory();
|
||||||
|
},
|
||||||
|
getCanonicalFileName(fileName: string): string {
|
||||||
|
return project.projectService.toCanonicalFileName(fileName);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTypeCheckingProgramStrategy(project: ts.server.Project):
|
||||||
|
TypeCheckingProgramStrategy {
|
||||||
|
return {
|
||||||
|
supportsInlineOperations: false,
|
||||||
|
shimPathForComponent(component: ts.ClassDeclaration): AbsoluteFsPath {
|
||||||
|
return TypeCheckShimGenerator.shimFor(absoluteFromSourceFile(component.getSourceFile()));
|
||||||
|
},
|
||||||
|
getProgram(): ts.Program {
|
||||||
|
const program = project.getLanguageService().getProgram();
|
||||||
|
if (!program) {
|
||||||
|
throw new Error('Language service does not have a program!');
|
||||||
|
}
|
||||||
|
return program;
|
||||||
|
},
|
||||||
|
updateFiles(contents: Map<AbsoluteFsPath, string>) {
|
||||||
|
for (const [fileName, newText] of contents) {
|
||||||
|
const scriptInfo = getOrCreateTypeCheckScriptInfo(project, fileName);
|
||||||
|
const snapshot = scriptInfo.getSnapshot();
|
||||||
|
const length = snapshot.getLength();
|
||||||
|
scriptInfo.editContent(0, length, newText);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOrCreateTypeCheckScriptInfo(
|
||||||
|
project: ts.server.Project, tcf: string): ts.server.ScriptInfo {
|
||||||
|
// First check if there is already a ScriptInfo for the tcf
|
||||||
|
const {projectService} = project;
|
||||||
|
let scriptInfo = projectService.getScriptInfo(tcf);
|
||||||
|
if (!scriptInfo) {
|
||||||
|
// ScriptInfo needs to be opened by client to be able to set its user-defined
|
||||||
|
// content. We must also provide file content, otherwise the service will
|
||||||
|
// attempt to fetch the content from disk and fail.
|
||||||
|
scriptInfo = projectService.getOrCreateScriptInfoForNormalizedPath(
|
||||||
|
ts.server.toNormalizedPath(tcf),
|
||||||
|
true, // openedByClient
|
||||||
|
'', // fileContent
|
||||||
|
ts.ScriptKind.TS, // scriptKind
|
||||||
|
);
|
||||||
|
if (!scriptInfo) {
|
||||||
|
throw new Error(`Failed to create script info for ${tcf}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add ScriptInfo to project if it's missing. A ScriptInfo needs to be part of
|
||||||
|
// the project so that it becomes part of the program.
|
||||||
|
if (!project.containsScriptInfo(scriptInfo)) {
|
||||||
|
project.addRoot(scriptInfo);
|
||||||
|
}
|
||||||
|
return scriptInfo;
|
||||||
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ jasmine_node_test(
|
||||||
],
|
],
|
||||||
tags = [
|
tags = [
|
||||||
"ivy-only",
|
"ivy-only",
|
||||||
"manual", # do not run this on CI since compiler APIs are not yet stable
|
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
":test_lib",
|
":test_lib",
|
||||||
|
|
Loading…
Reference in New Issue