mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-15 12:51:30 -05:00
Merge branch 'master' of https://github.com/Microsoft/TypeScript into 11116
This commit is contained in:
@@ -5,15 +5,6 @@
|
||||
|
||||
namespace ts.server {
|
||||
|
||||
interface Hash {
|
||||
update(data: any, input_encoding?: string): Hash;
|
||||
digest(encoding: string): any;
|
||||
}
|
||||
|
||||
const crypto: {
|
||||
createHash(algorithm: string): Hash
|
||||
} = require("crypto");
|
||||
|
||||
export function shouldEmitFile(scriptInfo: ScriptInfo) {
|
||||
return !scriptInfo.hasMixedContent;
|
||||
}
|
||||
@@ -49,9 +40,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private computeHash(text: string): string {
|
||||
return crypto.createHash("md5")
|
||||
.update(text)
|
||||
.digest("base64");
|
||||
return this.project.projectService.host.createHash(text);
|
||||
}
|
||||
|
||||
private getSourceFile(): SourceFile {
|
||||
@@ -86,17 +75,32 @@ namespace ts.server {
|
||||
getFilesAffectedBy(scriptInfo: ScriptInfo): string[];
|
||||
onProjectUpdateGraph(): void;
|
||||
emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean;
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
abstract class AbstractBuilder<T extends BuilderFileInfo> implements Builder {
|
||||
|
||||
private fileInfos = createFileMap<T>();
|
||||
/**
|
||||
* stores set of files from the project.
|
||||
* NOTE: this field is created on demand and should not be accessed directly.
|
||||
* Use 'getFileInfos' instead.
|
||||
*/
|
||||
private fileInfos_doNotAccessDirectly: FileMap<T>;
|
||||
|
||||
constructor(public readonly project: Project, private ctor: { new (scriptInfo: ScriptInfo, project: Project): T }) {
|
||||
}
|
||||
|
||||
private getFileInfos() {
|
||||
return this.fileInfos_doNotAccessDirectly || (this.fileInfos_doNotAccessDirectly = createFileMap<T>());
|
||||
}
|
||||
|
||||
public clear() {
|
||||
// drop the existing list - it will be re-created as necessary
|
||||
this.fileInfos_doNotAccessDirectly = undefined;
|
||||
}
|
||||
|
||||
protected getFileInfo(path: Path): T {
|
||||
return this.fileInfos.get(path);
|
||||
return this.getFileInfos().get(path);
|
||||
}
|
||||
|
||||
protected getOrCreateFileInfo(path: Path): T {
|
||||
@@ -110,19 +114,19 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
protected getFileInfoPaths(): Path[] {
|
||||
return this.fileInfos.getKeys();
|
||||
return this.getFileInfos().getKeys();
|
||||
}
|
||||
|
||||
protected setFileInfo(path: Path, info: T) {
|
||||
this.fileInfos.set(path, info);
|
||||
this.getFileInfos().set(path, info);
|
||||
}
|
||||
|
||||
protected removeFileInfo(path: Path) {
|
||||
this.fileInfos.remove(path);
|
||||
this.getFileInfos().remove(path);
|
||||
}
|
||||
|
||||
protected forEachFileInfo(action: (fileInfo: T) => any) {
|
||||
this.fileInfos.forEachValue((path: Path, value: T) => action(value));
|
||||
this.getFileInfos().forEachValue((_path, value) => action(value));
|
||||
}
|
||||
|
||||
abstract getFilesAffectedBy(scriptInfo: ScriptInfo): string[];
|
||||
@@ -242,6 +246,11 @@ namespace ts.server {
|
||||
|
||||
private projectVersionForDependencyGraph: string;
|
||||
|
||||
public clear() {
|
||||
this.projectVersionForDependencyGraph = undefined;
|
||||
super.clear();
|
||||
}
|
||||
|
||||
private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): ModuleBuilderFileInfo[] {
|
||||
if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) {
|
||||
return [];
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
"stripInternal": true,
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
],
|
||||
"target": "es5",
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
},
|
||||
"files": [
|
||||
"cancellationToken.ts"
|
||||
|
||||
@@ -5,11 +5,6 @@ namespace ts.server {
|
||||
writeMessage(message: string): void;
|
||||
}
|
||||
|
||||
interface CompletionEntry extends CompletionInfo {
|
||||
fileName: string;
|
||||
position: number;
|
||||
}
|
||||
|
||||
interface RenameEntry extends RenameInfo {
|
||||
fileName: string;
|
||||
position: number;
|
||||
@@ -246,8 +241,8 @@ namespace ts.server {
|
||||
return response.body[0];
|
||||
}
|
||||
|
||||
getCompletionEntrySymbol(fileName: string, position: number, entryName: string): Symbol {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
getCompletionEntrySymbol(_fileName: string, _position: number, _entryName: string): Symbol {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getNavigateToItems(searchValue: string): NavigateToItem[] {
|
||||
@@ -278,7 +273,7 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): ts.TextChange[] {
|
||||
getFormattingEditsForRange(fileName: string, start: number, end: number, _options: ts.FormatCodeOptions): ts.TextChange[] {
|
||||
const startLineOffset = this.positionToOneBasedLineOffset(fileName, start);
|
||||
const endLineOffset = this.positionToOneBasedLineOffset(fileName, end);
|
||||
const args: protocol.FormatRequestArgs = {
|
||||
@@ -300,7 +295,7 @@ namespace ts.server {
|
||||
return this.getFormattingEditsForRange(fileName, 0, this.host.getScriptSnapshot(fileName).getLength(), options);
|
||||
}
|
||||
|
||||
getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): ts.TextChange[] {
|
||||
getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, _options: FormatCodeOptions): ts.TextChange[] {
|
||||
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
|
||||
const args: protocol.FormatOnKeyRequestArgs = {
|
||||
file: fileName,
|
||||
@@ -390,7 +385,7 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
findReferences(fileName: string, position: number): ReferencedSymbol[] {
|
||||
findReferences(_fileName: string, _position: number): ReferencedSymbol[] {
|
||||
// Not yet implemented.
|
||||
return [];
|
||||
}
|
||||
@@ -419,44 +414,50 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
getEmitOutput(fileName: string): EmitOutput {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
getEmitOutput(_fileName: string): EmitOutput {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getSyntacticDiagnostics(fileName: string): Diagnostic[] {
|
||||
const args: protocol.SyntacticDiagnosticsSyncRequestArgs = { file: fileName };
|
||||
const args: protocol.SyntacticDiagnosticsSyncRequestArgs = { file: fileName, includeLinePosition: true };
|
||||
|
||||
const request = this.processRequest<protocol.SyntacticDiagnosticsSyncRequest>(CommandNames.SyntacticDiagnosticsSync, args);
|
||||
const response = this.processResponse<protocol.SyntacticDiagnosticsSyncResponse>(request);
|
||||
|
||||
return (<protocol.Diagnostic[]>response.body).map(entry => this.convertDiagnostic(entry, fileName));
|
||||
return (<protocol.DiagnosticWithLinePosition[]>response.body).map(entry => this.convertDiagnostic(entry, fileName));
|
||||
}
|
||||
|
||||
getSemanticDiagnostics(fileName: string): Diagnostic[] {
|
||||
const args: protocol.SemanticDiagnosticsSyncRequestArgs = { file: fileName };
|
||||
const args: protocol.SemanticDiagnosticsSyncRequestArgs = { file: fileName, includeLinePosition: true };
|
||||
|
||||
const request = this.processRequest<protocol.SemanticDiagnosticsSyncRequest>(CommandNames.SemanticDiagnosticsSync, args);
|
||||
const response = this.processResponse<protocol.SemanticDiagnosticsSyncResponse>(request);
|
||||
|
||||
return (<protocol.Diagnostic[]>response.body).map(entry => this.convertDiagnostic(entry, fileName));
|
||||
return (<protocol.DiagnosticWithLinePosition[]>response.body).map(entry => this.convertDiagnostic(entry, fileName));
|
||||
}
|
||||
|
||||
convertDiagnostic(entry: protocol.Diagnostic, fileName: string): Diagnostic {
|
||||
const start = this.lineOffsetToPosition(fileName, entry.start);
|
||||
const end = this.lineOffsetToPosition(fileName, entry.end);
|
||||
convertDiagnostic(entry: protocol.DiagnosticWithLinePosition, _fileName: string): Diagnostic {
|
||||
let category: DiagnosticCategory;
|
||||
for (const id in DiagnosticCategory) {
|
||||
if (typeof id === "string" && entry.category === id.toLowerCase()) {
|
||||
category = (<any>DiagnosticCategory)[id];
|
||||
}
|
||||
}
|
||||
|
||||
Debug.assert(category !== undefined, "convertDiagnostic: category should not be undefined");
|
||||
|
||||
return {
|
||||
file: undefined,
|
||||
start: start,
|
||||
length: end - start,
|
||||
messageText: entry.text,
|
||||
category: undefined,
|
||||
start: entry.start,
|
||||
length: entry.length,
|
||||
messageText: entry.message,
|
||||
category: category,
|
||||
code: entry.code
|
||||
};
|
||||
}
|
||||
|
||||
getCompilerOptionsDiagnostics(): Diagnostic[] {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getRenameInfo(fileName: string, position: number, findInStrings?: boolean, findInComments?: boolean): RenameInfo {
|
||||
@@ -560,12 +561,12 @@ namespace ts.server {
|
||||
this.lineOffsetToPosition(fileName, span.end, lineMap));
|
||||
}
|
||||
|
||||
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
getNameOrDottedNameSpan(_fileName: string, _startPos: number, _endPos: number): TextSpan {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
getBreakpointStatementAtPosition(_fileName: string, _position: number): TextSpan {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems {
|
||||
@@ -654,20 +655,20 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
getOutliningSpans(fileName: string): OutliningSpan[] {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
getOutliningSpans(_fileName: string): OutliningSpan[] {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
getTodoComments(_fileName: string, _descriptors: TodoCommentDescriptor[]): TodoComment[] {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
getDocCommentTemplateAtPosition(_fileName: string, _position: number): TextInsertion {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
isValidBraceCompletionAtPosition(_fileName: string, _position: number, _openingBrace: number): boolean {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): CodeAction[] {
|
||||
@@ -733,35 +734,35 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
getIndentationAtPosition(fileName: string, position: number, options: EditorOptions): number {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
getIndentationAtPosition(_fileName: string, _position: number, _options: EditorOptions): number {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
getSyntacticClassifications(_fileName: string, _span: TextSpan): ClassifiedSpan[] {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
getSemanticClassifications(_fileName: string, _span: TextSpan): ClassifiedSpan[] {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
getEncodedSyntacticClassifications(_fileName: string, _span: TextSpan): Classifications {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
getEncodedSemanticClassifications(_fileName: string, _span: TextSpan): Classifications {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getProgram(): Program {
|
||||
throw new Error("SourceFile objects are not serializable through the server protocol.");
|
||||
}
|
||||
|
||||
getNonBoundSourceFile(fileName: string): SourceFile {
|
||||
getNonBoundSourceFile(_fileName: string): SourceFile {
|
||||
throw new Error("SourceFile objects are not serializable through the server protocol.");
|
||||
}
|
||||
|
||||
getSourceFile(fileName: string): SourceFile {
|
||||
getSourceFile(_fileName: string): SourceFile {
|
||||
throw new Error("SourceFile objects are not serializable through the server protocol.");
|
||||
}
|
||||
|
||||
|
||||
@@ -10,13 +10,93 @@
|
||||
namespace ts.server {
|
||||
export const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024;
|
||||
|
||||
export type ProjectServiceEvent =
|
||||
{ eventName: "context", data: { project: Project, fileName: NormalizedPath } } | { eventName: "configFileDiag", data: { triggerFile?: string, configFileName: string, diagnostics: Diagnostic[] } };
|
||||
export const ContextEvent = "context";
|
||||
export const ConfigFileDiagEvent = "configFileDiag";
|
||||
export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState";
|
||||
|
||||
export interface ContextEvent {
|
||||
eventName: typeof ContextEvent;
|
||||
data: { project: Project; fileName: NormalizedPath };
|
||||
}
|
||||
|
||||
export interface ConfigFileDiagEvent {
|
||||
eventName: typeof ConfigFileDiagEvent;
|
||||
data: { triggerFile: string, configFileName: string, diagnostics: Diagnostic[] };
|
||||
}
|
||||
|
||||
export interface ProjectLanguageServiceStateEvent {
|
||||
eventName: typeof ProjectLanguageServiceStateEvent;
|
||||
data: { project: Project, languageServiceEnabled: boolean };
|
||||
}
|
||||
|
||||
export type ProjectServiceEvent = ContextEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent;
|
||||
|
||||
export interface ProjectServiceEventHandler {
|
||||
(event: ProjectServiceEvent): void;
|
||||
}
|
||||
|
||||
function prepareConvertersForEnumLikeCompilerOptions(commandLineOptions: CommandLineOption[]): Map<Map<number>> {
|
||||
const map: Map<Map<number>> = createMap<Map<number>>();
|
||||
for (const option of commandLineOptions) {
|
||||
if (typeof option.type === "object") {
|
||||
const optionMap = <Map<number>>option.type;
|
||||
// verify that map contains only numbers
|
||||
for (const id in optionMap) {
|
||||
Debug.assert(typeof optionMap[id] === "number");
|
||||
}
|
||||
map[option.name] = optionMap;
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
const compilerOptionConverters = prepareConvertersForEnumLikeCompilerOptions(optionDeclarations);
|
||||
const indentStyle = createMap({
|
||||
"none": IndentStyle.None,
|
||||
"block": IndentStyle.Block,
|
||||
"smart": IndentStyle.Smart
|
||||
});
|
||||
|
||||
export function convertFormatOptions(protocolOptions: protocol.FormatCodeSettings): FormatCodeSettings {
|
||||
if (typeof protocolOptions.indentStyle === "string") {
|
||||
protocolOptions.indentStyle = indentStyle[protocolOptions.indentStyle.toLowerCase()];
|
||||
Debug.assert(protocolOptions.indentStyle !== undefined);
|
||||
}
|
||||
return <any>protocolOptions;
|
||||
}
|
||||
|
||||
export function convertCompilerOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): CompilerOptions & protocol.CompileOnSaveMixin {
|
||||
for (const id in compilerOptionConverters) {
|
||||
const propertyValue = protocolOptions[id];
|
||||
if (typeof propertyValue === "string") {
|
||||
const mappedValues = compilerOptionConverters[id];
|
||||
protocolOptions[id] = mappedValues[propertyValue.toLowerCase()];
|
||||
}
|
||||
}
|
||||
return <any>protocolOptions;
|
||||
}
|
||||
|
||||
export function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind {
|
||||
return typeof scriptKindName === "string"
|
||||
? convertScriptKindName(scriptKindName)
|
||||
: scriptKindName;
|
||||
}
|
||||
|
||||
export function convertScriptKindName(scriptKindName: protocol.ScriptKindName) {
|
||||
switch (scriptKindName) {
|
||||
case "JS":
|
||||
return ScriptKind.JS;
|
||||
case "JSX":
|
||||
return ScriptKind.JSX;
|
||||
case "TS":
|
||||
return ScriptKind.TS;
|
||||
case "TSX":
|
||||
return ScriptKind.TSX;
|
||||
default:
|
||||
return ScriptKind.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project.
|
||||
*/
|
||||
@@ -28,6 +108,7 @@ namespace ts.server {
|
||||
export interface HostConfiguration {
|
||||
formatCodeOptions: FormatCodeSettings;
|
||||
hostInfo: string;
|
||||
extraFileExtensions?: FileExtensionInfo[];
|
||||
}
|
||||
|
||||
interface ConfigFileConversionResult {
|
||||
@@ -45,25 +126,28 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
export interface OpenConfiguredProjectResult {
|
||||
configFileName?: string;
|
||||
configFileName?: NormalizedPath;
|
||||
configFileErrors?: Diagnostic[];
|
||||
}
|
||||
|
||||
interface FilePropertyReader<T> {
|
||||
getFileName(f: T): string;
|
||||
getScriptKind(f: T): ScriptKind;
|
||||
hasMixedContent(f: T): boolean;
|
||||
hasMixedContent(f: T, extraFileExtensions: FileExtensionInfo[]): boolean;
|
||||
}
|
||||
|
||||
const fileNamePropertyReader: FilePropertyReader<string> = {
|
||||
getFileName: x => x,
|
||||
getScriptKind: _ => undefined,
|
||||
hasMixedContent: _ => false
|
||||
hasMixedContent: (fileName, extraFileExtensions) => {
|
||||
const mixedContentExtensions = ts.map(ts.filter(extraFileExtensions, item => item.isMixedContent), item => item.extension);
|
||||
return forEach(mixedContentExtensions, extension => fileExtensionIs(fileName, extension))
|
||||
}
|
||||
};
|
||||
|
||||
const externalFilePropertyReader: FilePropertyReader<protocol.ExternalFile> = {
|
||||
getFileName: x => x.fileName,
|
||||
getScriptKind: x => x.scriptKind,
|
||||
getScriptKind: x => tryConvertScriptKindName(x.scriptKind),
|
||||
hasMixedContent: x => x.hasMixedContent
|
||||
};
|
||||
|
||||
@@ -177,7 +261,7 @@ namespace ts.server {
|
||||
|
||||
private changedFiles: ScriptInfo[];
|
||||
|
||||
private toCanonicalFileName: (f: string) => string;
|
||||
readonly toCanonicalFileName: (f: string) => string;
|
||||
|
||||
public lastDeletedFile: ScriptInfo;
|
||||
|
||||
@@ -188,6 +272,8 @@ namespace ts.server {
|
||||
readonly typingsInstaller: ITypingsInstaller = nullTypingsInstaller,
|
||||
private readonly eventHandler?: ProjectServiceEventHandler) {
|
||||
|
||||
Debug.assert(!!host.createHash, "'ServerHost.createHash' is required for ProjectService");
|
||||
|
||||
this.toCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
|
||||
this.directoryWatchers = new DirectoryWatchers(this);
|
||||
this.throttledOperations = new ThrottledOperations(host);
|
||||
@@ -200,7 +286,8 @@ namespace ts.server {
|
||||
|
||||
this.hostConfiguration = {
|
||||
formatCodeOptions: getDefaultFormatCodeSettings(this.host),
|
||||
hostInfo: "Unknown host"
|
||||
hostInfo: "Unknown host",
|
||||
extraFileExtensions: []
|
||||
};
|
||||
|
||||
this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames, host.getCurrentDirectory());
|
||||
@@ -214,27 +301,44 @@ namespace ts.server {
|
||||
this.ensureInferredProjectsUpToDate();
|
||||
}
|
||||
|
||||
getCompilerOptionsForInferredProjects() {
|
||||
return this.compilerOptionsForInferredProjects;
|
||||
}
|
||||
|
||||
onUpdateLanguageServiceStateForProject(project: Project, languageServiceEnabled: boolean) {
|
||||
if (!this.eventHandler) {
|
||||
return;
|
||||
}
|
||||
this.eventHandler(<ProjectLanguageServiceStateEvent>{
|
||||
eventName: ProjectLanguageServiceStateEvent,
|
||||
data: { project, languageServiceEnabled }
|
||||
});
|
||||
}
|
||||
|
||||
updateTypingsForProject(response: SetTypings | InvalidateCachedTypings): void {
|
||||
const project = this.findProject(response.projectName);
|
||||
if (!project) {
|
||||
return;
|
||||
}
|
||||
switch (response.kind) {
|
||||
case "set":
|
||||
this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typingOptions, response.typings);
|
||||
project.updateGraph();
|
||||
case ActionSet:
|
||||
this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typeAcquisition, response.unresolvedImports, response.typings);
|
||||
break;
|
||||
case "invalidate":
|
||||
this.typingsCache.invalidateCachedTypingsForProject(project);
|
||||
case ActionInvalidate:
|
||||
this.typingsCache.deleteTypingsForProject(response.projectName);
|
||||
break;
|
||||
}
|
||||
project.updateGraph();
|
||||
}
|
||||
|
||||
setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.ExternalProjectCompilerOptions): void {
|
||||
this.compilerOptionsForInferredProjects = projectCompilerOptions;
|
||||
this.compilerOptionsForInferredProjects = convertCompilerOptions(projectCompilerOptions);
|
||||
// always set 'allowNonTsExtensions' for inferred projects since user cannot configure it from the outside
|
||||
// previously we did not expose a way for user to change these settings and this option was enabled by default
|
||||
this.compilerOptionsForInferredProjects.allowNonTsExtensions = true;
|
||||
this.compileOnSaveForInferredProjects = projectCompilerOptions.compileOnSave;
|
||||
for (const proj of this.inferredProjects) {
|
||||
proj.setCompilerOptions(projectCompilerOptions);
|
||||
proj.setCompilerOptions(this.compilerOptionsForInferredProjects);
|
||||
proj.compileOnSaveEnabled = projectCompilerOptions.compileOnSave;
|
||||
}
|
||||
this.updateProjectGraphs(this.inferredProjects);
|
||||
@@ -325,7 +429,7 @@ namespace ts.server {
|
||||
this.handleDeletedFile(info);
|
||||
}
|
||||
else {
|
||||
if (info && (!info.isOpen)) {
|
||||
if (info && (!info.isScriptOpen())) {
|
||||
// file has been changed which might affect the set of referenced files in projects that include
|
||||
// this file and set of inferred projects
|
||||
info.reloadFromFile();
|
||||
@@ -341,7 +445,7 @@ namespace ts.server {
|
||||
|
||||
// TODO: handle isOpen = true case
|
||||
|
||||
if (!info.isOpen) {
|
||||
if (!info.isScriptOpen()) {
|
||||
this.filenameToScriptInfo.remove(info.path);
|
||||
this.lastDeletedFile = info;
|
||||
|
||||
@@ -359,7 +463,10 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
for (const openFile of this.openFiles) {
|
||||
this.eventHandler({ eventName: "context", data: { project: openFile.getDefaultProject(), fileName: openFile.fileName } });
|
||||
this.eventHandler(<ContextEvent>{
|
||||
eventName: ContextEvent,
|
||||
data: { project: openFile.getDefaultProject(), fileName: openFile.fileName }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +475,7 @@ namespace ts.server {
|
||||
|
||||
private onTypeRootFileChanged(project: ConfiguredProject, fileName: string) {
|
||||
this.logger.info(`Type root file ${fileName} changed`);
|
||||
this.throttledOperations.schedule(project.configFileName + " * type root", /*delay*/ 250, () => {
|
||||
this.throttledOperations.schedule(project.getConfigFilePath() + " * type root", /*delay*/ 250, () => {
|
||||
project.updateTypes();
|
||||
this.updateConfiguredProject(project); // TODO: Figure out why this is needed (should be redundant?)
|
||||
this.refreshInferredProjects();
|
||||
@@ -384,20 +491,20 @@ namespace ts.server {
|
||||
// If a change was made inside "folder/file", node will trigger the callback twice:
|
||||
// one with the fileName being "folder/file", and the other one with "folder".
|
||||
// We don't respond to the second one.
|
||||
if (fileName && !ts.isSupportedSourceFileName(fileName, project.getCompilerOptions())) {
|
||||
if (fileName && !ts.isSupportedSourceFileName(fileName, project.getCompilerOptions(), this.hostConfiguration.extraFileExtensions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.info(`Detected source file changes: ${fileName}`);
|
||||
this.throttledOperations.schedule(
|
||||
project.configFileName,
|
||||
project.getConfigFilePath(),
|
||||
/*delay*/250,
|
||||
() => this.handleChangeInSourceFileForConfiguredProject(project));
|
||||
() => this.handleChangeInSourceFileForConfiguredProject(project, fileName));
|
||||
}
|
||||
|
||||
private handleChangeInSourceFileForConfiguredProject(project: ConfiguredProject) {
|
||||
const { projectOptions, configFileErrors } = this.convertConfigFileContentToProjectOptions(project.configFileName);
|
||||
this.reportConfigFileDiagnostics(project.getProjectName(), configFileErrors);
|
||||
private handleChangeInSourceFileForConfiguredProject(project: ConfiguredProject, triggerFile: string) {
|
||||
const { projectOptions, configFileErrors } = this.convertConfigFileContentToProjectOptions(project.getConfigFilePath());
|
||||
this.reportConfigFileDiagnostics(project.getProjectName(), configFileErrors, triggerFile);
|
||||
|
||||
const newRootFiles = projectOptions.files.map((f => this.getCanonicalFileName(f)));
|
||||
const currentRootFiles = project.getRootFiles().map((f => this.getCanonicalFileName(f)));
|
||||
@@ -418,8 +525,10 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private onConfigChangedForConfiguredProject(project: ConfiguredProject) {
|
||||
this.logger.info(`Config file changed: ${project.configFileName}`);
|
||||
this.updateConfiguredProject(project);
|
||||
const configFileName = project.getConfigFilePath();
|
||||
this.logger.info(`Config file changed: ${configFileName}`);
|
||||
const configFileErrors = this.updateConfiguredProject(project);
|
||||
this.reportConfigFileDiagnostics(configFileName, configFileErrors, /*triggerFile*/ configFileName);
|
||||
this.refreshInferredProjects();
|
||||
}
|
||||
|
||||
@@ -434,7 +543,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
const { configFileErrors } = this.convertConfigFileContentToProjectOptions(fileName);
|
||||
this.reportConfigFileDiagnostics(fileName, configFileErrors);
|
||||
this.reportConfigFileDiagnostics(fileName, configFileErrors, fileName);
|
||||
|
||||
this.logger.info(`Detected newly added tsconfig file: ${fileName}`);
|
||||
this.reloadProjects();
|
||||
@@ -530,15 +639,17 @@ namespace ts.server {
|
||||
// Closing file should trigger re-reading the file content from disk. This is
|
||||
// because the user may chose to discard the buffer content before saving
|
||||
// to the disk, and the server's version of the file can be out of sync.
|
||||
info.reloadFromFile();
|
||||
info.close();
|
||||
|
||||
removeItemFromSet(this.openFiles, info);
|
||||
info.isOpen = false;
|
||||
|
||||
// collect all projects that should be removed
|
||||
let projectsToRemove: Project[];
|
||||
for (const p of info.containingProjects) {
|
||||
if (p.projectKind === ProjectKind.Configured) {
|
||||
if (info.hasMixedContent) {
|
||||
info.registerFileUpdate();
|
||||
}
|
||||
// last open file in configured project - close it
|
||||
if ((<ConfiguredProject>p).deleteOpenRef() === 0) {
|
||||
(projectsToRemove || (projectsToRemove = [])).push(p);
|
||||
@@ -548,6 +659,13 @@ namespace ts.server {
|
||||
// open file in inferred project
|
||||
(projectsToRemove || (projectsToRemove = [])).push(p);
|
||||
}
|
||||
|
||||
if (!p.languageServiceEnabled) {
|
||||
// if project language service is disabled then we create a program only for open files.
|
||||
// this means that project should be marked as dirty to force rebuilding of the program
|
||||
// on the next request
|
||||
p.markAsDirty();
|
||||
}
|
||||
}
|
||||
if (projectsToRemove) {
|
||||
for (const project of projectsToRemove) {
|
||||
@@ -673,7 +791,13 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private findConfiguredProjectByProjectName(configFileName: NormalizedPath) {
|
||||
return findProjectByName(configFileName, this.configuredProjects);
|
||||
// make sure that casing of config file name is consistent
|
||||
configFileName = asNormalizedPath(this.toCanonicalFileName(configFileName));
|
||||
for (const proj of this.configuredProjects) {
|
||||
if (proj.canonicalConfigFilePath === configFileName) {
|
||||
return proj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private findExternalProjectByProjectName(projectFileName: string) {
|
||||
@@ -701,7 +825,9 @@ namespace ts.server {
|
||||
this.host,
|
||||
getDirectoryPath(configFilename),
|
||||
/*existingOptions*/ {},
|
||||
configFilename);
|
||||
configFilename,
|
||||
/*resolutionStack*/ [],
|
||||
this.hostConfiguration.extraFileExtensions);
|
||||
|
||||
if (parsedCommandLine.errors.length) {
|
||||
errors = concatenate(errors, parsedCommandLine.errors);
|
||||
@@ -710,7 +836,7 @@ namespace ts.server {
|
||||
Debug.assert(!!parsedCommandLine.fileNames);
|
||||
|
||||
if (parsedCommandLine.fileNames.length === 0) {
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename));
|
||||
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename));
|
||||
return { success: false, configFileErrors: errors };
|
||||
}
|
||||
|
||||
@@ -719,7 +845,7 @@ namespace ts.server {
|
||||
compilerOptions: parsedCommandLine.options,
|
||||
configHasFilesProperty: config["files"] !== undefined,
|
||||
wildcardDirectories: createMap(parsedCommandLine.wildcardDirectories),
|
||||
typingOptions: parsedCommandLine.typingOptions,
|
||||
typeAcquisition: parsedCommandLine.typeAcquisition,
|
||||
compileOnSave: parsedCommandLine.compileOnSave
|
||||
};
|
||||
return { success: true, projectOptions, configFileErrors: errors };
|
||||
@@ -743,27 +869,28 @@ namespace ts.server {
|
||||
return false;
|
||||
}
|
||||
|
||||
private createAndAddExternalProject(projectFileName: string, files: protocol.ExternalFile[], options: protocol.ExternalProjectCompilerOptions, typingOptions: TypingOptions) {
|
||||
private createAndAddExternalProject(projectFileName: string, files: protocol.ExternalFile[], options: protocol.ExternalProjectCompilerOptions, typeAcquisition: TypeAcquisition) {
|
||||
const compilerOptions = convertCompilerOptions(options);
|
||||
const project = new ExternalProject(
|
||||
projectFileName,
|
||||
this,
|
||||
this.documentRegistry,
|
||||
options,
|
||||
/*languageServiceEnabled*/ !this.exceededTotalSizeLimitForNonTsFiles(options, files, externalFilePropertyReader),
|
||||
compilerOptions,
|
||||
/*languageServiceEnabled*/ !this.exceededTotalSizeLimitForNonTsFiles(compilerOptions, files, externalFilePropertyReader),
|
||||
options.compileOnSave === undefined ? true : options.compileOnSave);
|
||||
|
||||
this.addFilesToProjectAndUpdateGraph(project, files, externalFilePropertyReader, /*clientFileName*/ undefined, typingOptions, /*configFileErrors*/ undefined);
|
||||
this.addFilesToProjectAndUpdateGraph(project, files, externalFilePropertyReader, /*clientFileName*/ undefined, typeAcquisition, /*configFileErrors*/ undefined);
|
||||
this.externalProjects.push(project);
|
||||
return project;
|
||||
}
|
||||
|
||||
private reportConfigFileDiagnostics(configFileName: string, diagnostics: Diagnostic[], triggerFile?: string) {
|
||||
private reportConfigFileDiagnostics(configFileName: string, diagnostics: Diagnostic[], triggerFile: string) {
|
||||
if (!this.eventHandler) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.eventHandler({
|
||||
eventName: "configFileDiag",
|
||||
this.eventHandler(<ConfigFileDiagEvent>{
|
||||
eventName: ConfigFileDiagEvent,
|
||||
data: { configFileName, diagnostics: diagnostics || [], triggerFile }
|
||||
});
|
||||
}
|
||||
@@ -780,7 +907,7 @@ namespace ts.server {
|
||||
/*languageServiceEnabled*/ !sizeLimitExceeded,
|
||||
projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave);
|
||||
|
||||
this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typingOptions, configFileErrors);
|
||||
this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors);
|
||||
|
||||
project.watchConfigFile(project => this.onConfigChangedForConfiguredProject(project));
|
||||
if (!sizeLimitExceeded) {
|
||||
@@ -799,12 +926,12 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
private addFilesToProjectAndUpdateGraph<T>(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader<T>, clientFileName: string, typingOptions: TypingOptions, configFileErrors: Diagnostic[]): void {
|
||||
private addFilesToProjectAndUpdateGraph<T>(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader<T>, clientFileName: string, typeAcquisition: TypeAcquisition, configFileErrors: Diagnostic[]): void {
|
||||
let errors: Diagnostic[];
|
||||
for (const f of files) {
|
||||
const rootFilename = propertyReader.getFileName(f);
|
||||
const scriptKind = propertyReader.getScriptKind(f);
|
||||
const hasMixedContent = propertyReader.hasMixedContent(f);
|
||||
const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions);
|
||||
if (this.host.fileExists(rootFilename)) {
|
||||
const info = this.getOrCreateScriptInfoForNormalizedPath(toNormalizedPath(rootFilename), /*openedByClient*/ clientFileName == rootFilename, /*fileContent*/ undefined, scriptKind, hasMixedContent);
|
||||
project.addRoot(info);
|
||||
@@ -814,7 +941,7 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
project.setProjectErrors(concatenate(configFileErrors, errors));
|
||||
project.setTypingOptions(typingOptions);
|
||||
project.setTypeAcquisition(typeAcquisition);
|
||||
project.updateGraph();
|
||||
}
|
||||
|
||||
@@ -831,7 +958,7 @@ namespace ts.server {
|
||||
};
|
||||
}
|
||||
|
||||
private updateNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypingOptions: TypingOptions, compileOnSave: boolean, configFileErrors: Diagnostic[]) {
|
||||
private updateNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean, configFileErrors: Diagnostic[]) {
|
||||
const oldRootScriptInfos = project.getRootScriptInfos();
|
||||
const newRootScriptInfos: ScriptInfo[] = [];
|
||||
const newRootScriptInfoMap: NormalizedPathMap<ScriptInfo> = createNormalizedPathMap<ScriptInfo>();
|
||||
@@ -850,7 +977,7 @@ namespace ts.server {
|
||||
rootFilesChanged = true;
|
||||
if (!scriptInfo) {
|
||||
const scriptKind = propertyReader.getScriptKind(f);
|
||||
const hasMixedContent = propertyReader.hasMixedContent(f);
|
||||
const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions);
|
||||
scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent);
|
||||
}
|
||||
}
|
||||
@@ -878,7 +1005,7 @@ namespace ts.server {
|
||||
}
|
||||
if (toAdd) {
|
||||
for (const f of toAdd) {
|
||||
if (f.isOpen && isRootFileInInferredProject(f)) {
|
||||
if (f.isScriptOpen() && isRootFileInInferredProject(f)) {
|
||||
// if file is already root in some inferred project
|
||||
// - remove the file from that project and delete the project if necessary
|
||||
const inferredProject = f.containingProjects[0];
|
||||
@@ -893,7 +1020,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
project.setCompilerOptions(newOptions);
|
||||
(<ExternalProject | ConfiguredProject>project).setTypingOptions(newTypingOptions);
|
||||
(<ExternalProject | ConfiguredProject>project).setTypeAcquisition(newTypeAcquisition);
|
||||
|
||||
// VS only set the CompileOnSaveEnabled option in the request if the option was changed recently
|
||||
// therefore if it is undefined, it should not be updated.
|
||||
@@ -906,13 +1033,16 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private updateConfiguredProject(project: ConfiguredProject) {
|
||||
if (!this.host.fileExists(project.configFileName)) {
|
||||
if (!this.host.fileExists(project.getConfigFilePath())) {
|
||||
this.logger.info("Config file deleted");
|
||||
this.removeProject(project);
|
||||
return;
|
||||
}
|
||||
|
||||
const { success, projectOptions, configFileErrors } = this.convertConfigFileContentToProjectOptions(project.configFileName);
|
||||
// note: the returned "success" is true does not mean the "configFileErrors" is empty.
|
||||
// because we might have tolerated the errors and kept going. So always return the configFileErrors
|
||||
// regardless the "success" here is true or not.
|
||||
const { success, projectOptions, configFileErrors } = this.convertConfigFileContentToProjectOptions(project.getConfigFilePath());
|
||||
if (!success) {
|
||||
// reset project settings to default
|
||||
this.updateNonInferredProject(project, [], fileNamePropertyReader, {}, {}, /*compileOnSave*/false, configFileErrors);
|
||||
@@ -923,25 +1053,24 @@ namespace ts.server {
|
||||
project.setCompilerOptions(projectOptions.compilerOptions);
|
||||
if (!project.languageServiceEnabled) {
|
||||
// language service is already disabled
|
||||
return;
|
||||
return configFileErrors;
|
||||
}
|
||||
project.disableLanguageService();
|
||||
project.stopWatchingDirectory();
|
||||
}
|
||||
else {
|
||||
if (!project.languageServiceEnabled) {
|
||||
project.enableLanguageService();
|
||||
}
|
||||
project.enableLanguageService();
|
||||
this.watchConfigDirectoryForProject(project, projectOptions);
|
||||
this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typingOptions, projectOptions.compileOnSave, configFileErrors);
|
||||
this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave, configFileErrors);
|
||||
}
|
||||
return configFileErrors;
|
||||
}
|
||||
|
||||
createInferredProjectWithRootFileIfNecessary(root: ScriptInfo) {
|
||||
const useExistingProject = this.useSingleInferredProject && this.inferredProjects.length;
|
||||
const project = useExistingProject
|
||||
? this.inferredProjects[0]
|
||||
: new InferredProject(this, this.documentRegistry, /*languageServiceEnabled*/ true, this.compilerOptionsForInferredProjects, /*compileOnSaveEnabled*/ this.compileOnSaveForInferredProjects);
|
||||
: new InferredProject(this, this.documentRegistry, this.compilerOptionsForInferredProjects);
|
||||
|
||||
project.addRoot(root);
|
||||
|
||||
@@ -974,32 +1103,34 @@ namespace ts.server {
|
||||
getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean) {
|
||||
let info = this.getScriptInfoForNormalizedPath(fileName);
|
||||
if (!info) {
|
||||
let content: string;
|
||||
if (this.host.fileExists(fileName)) {
|
||||
// by default pick whatever content was supplied as the argument
|
||||
// if argument was not given - then for mixed content files assume that its content is empty string
|
||||
content = fileContent || (hasMixedContent ? "" : this.host.readFile(fileName));
|
||||
}
|
||||
if (!content) {
|
||||
if (openedByClient) {
|
||||
content = "";
|
||||
}
|
||||
}
|
||||
if (content !== undefined) {
|
||||
info = new ScriptInfo(this.host, fileName, content, scriptKind, openedByClient, hasMixedContent);
|
||||
// do not watch files with mixed content - server doesn't know how to interpret it
|
||||
if (openedByClient || this.host.fileExists(fileName)) {
|
||||
info = new ScriptInfo(this.host, fileName, scriptKind, hasMixedContent);
|
||||
|
||||
this.filenameToScriptInfo.set(info.path, info);
|
||||
if (!info.isOpen && !hasMixedContent) {
|
||||
info.setWatcher(this.host.watchFile(fileName, _ => this.onSourceFileChanged(fileName)));
|
||||
|
||||
if (openedByClient) {
|
||||
if (fileContent === undefined) {
|
||||
// if file is opened by client and its content is not specified - use file text
|
||||
fileContent = this.host.readFile(fileName) || "";
|
||||
}
|
||||
}
|
||||
else {
|
||||
// do not watch files with mixed content - server doesn't know how to interpret it
|
||||
if (!hasMixedContent) {
|
||||
info.setWatcher(this.host.watchFile(fileName, _ => this.onSourceFileChanged(fileName)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (info) {
|
||||
if (fileContent !== undefined) {
|
||||
info.reload(fileContent);
|
||||
if (openedByClient && !info.isScriptOpen()) {
|
||||
info.open(fileContent);
|
||||
if (hasMixedContent) {
|
||||
info.registerFileUpdate();
|
||||
}
|
||||
}
|
||||
if (openedByClient) {
|
||||
info.isOpen = true;
|
||||
else if (fileContent !== undefined) {
|
||||
info.reload(fileContent);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
@@ -1018,7 +1149,7 @@ namespace ts.server {
|
||||
if (args.file) {
|
||||
const info = this.getScriptInfoForNormalizedPath(toNormalizedPath(args.file));
|
||||
if (info) {
|
||||
info.setFormatOptions(args.formatOptions);
|
||||
info.setFormatOptions(convertFormatOptions(args.formatOptions));
|
||||
this.logger.info(`Host configuration update for file ${args.file}`);
|
||||
}
|
||||
}
|
||||
@@ -1028,9 +1159,13 @@ namespace ts.server {
|
||||
this.logger.info(`Host information ${args.hostInfo}`);
|
||||
}
|
||||
if (args.formatOptions) {
|
||||
mergeMaps(this.hostConfiguration.formatCodeOptions, args.formatOptions);
|
||||
mergeMaps(this.hostConfiguration.formatCodeOptions, convertFormatOptions(args.formatOptions));
|
||||
this.logger.info("Format host information updated");
|
||||
}
|
||||
if (args.extraFileExtensions) {
|
||||
this.hostConfiguration.extraFileExtensions = args.extraFileExtensions;
|
||||
this.logger.info("Host file extension mappings updated");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1096,9 +1231,22 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean): OpenConfiguredProjectResult {
|
||||
const { configFileName = undefined, configFileErrors = undefined }: OpenConfiguredProjectResult = this.findContainingExternalProject(fileName)
|
||||
? {}
|
||||
: this.openOrUpdateConfiguredProjectForFile(fileName);
|
||||
let configFileName: NormalizedPath;
|
||||
let configFileErrors: Diagnostic[];
|
||||
|
||||
let project: ConfiguredProject | ExternalProject = this.findContainingExternalProject(fileName);
|
||||
if (!project) {
|
||||
({ configFileName, configFileErrors } = this.openOrUpdateConfiguredProjectForFile(fileName));
|
||||
if (configFileName) {
|
||||
project = this.findConfiguredProjectByProjectName(configFileName);
|
||||
}
|
||||
}
|
||||
if (project && !project.languageServiceEnabled) {
|
||||
// if project language service is disabled then we create a program only for open files.
|
||||
// this means that project should be marked as dirty to force rebuilding of the program
|
||||
// on the next request
|
||||
project.markAsDirty();
|
||||
}
|
||||
|
||||
// at this point if file is the part of some configured/external project then this project should be created
|
||||
const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent);
|
||||
@@ -1115,7 +1263,6 @@ namespace ts.server {
|
||||
const info = this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
|
||||
if (info) {
|
||||
this.closeOpenFile(info);
|
||||
info.isOpen = false;
|
||||
}
|
||||
this.printProjects();
|
||||
}
|
||||
@@ -1140,9 +1287,9 @@ namespace ts.server {
|
||||
if (openFiles) {
|
||||
for (const file of openFiles) {
|
||||
const scriptInfo = this.getScriptInfo(file.fileName);
|
||||
Debug.assert(!scriptInfo || !scriptInfo.isOpen);
|
||||
Debug.assert(!scriptInfo || !scriptInfo.isScriptOpen());
|
||||
const normalizedPath = scriptInfo ? scriptInfo.fileName : toNormalizedPath(file.fileName);
|
||||
this.openClientFileWithNormalizedPath(normalizedPath, file.content, file.scriptKind, file.hasMixedContent);
|
||||
this.openClientFileWithNormalizedPath(normalizedPath, file.content, tryConvertScriptKindName(file.scriptKind), file.hasMixedContent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1212,13 +1359,42 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
openExternalProject(proj: protocol.ExternalProject): void {
|
||||
openExternalProjects(projects: protocol.ExternalProject[]): void {
|
||||
// record project list before the update
|
||||
const projectsToClose = arrayToMap(this.externalProjects, p => p.getProjectName(), _ => true);
|
||||
for (const externalProjectName in this.externalProjectToConfiguredProjectMap) {
|
||||
projectsToClose[externalProjectName] = true;
|
||||
}
|
||||
|
||||
for (const externalProject of projects) {
|
||||
this.openExternalProject(externalProject, /*suppressRefreshOfInferredProjects*/ true);
|
||||
// delete project that is present in input list
|
||||
delete projectsToClose[externalProject.projectFileName];
|
||||
}
|
||||
|
||||
// close projects that were missing in the input list
|
||||
for (const externalProjectName in projectsToClose) {
|
||||
this.closeExternalProject(externalProjectName, /*suppressRefresh*/ true)
|
||||
}
|
||||
|
||||
this.refreshInferredProjects();
|
||||
}
|
||||
|
||||
openExternalProject(proj: protocol.ExternalProject, suppressRefreshOfInferredProjects = false): void {
|
||||
// typingOptions has been deprecated and is only supported for backward compatibility
|
||||
// purposes. It should be removed in future releases - use typeAcquisition instead.
|
||||
if (proj.typingOptions && !proj.typeAcquisition) {
|
||||
const typeAcquisition = convertEnableAutoDiscoveryToEnable(proj.typingOptions);
|
||||
proj.typeAcquisition = typeAcquisition;
|
||||
}
|
||||
let tsConfigFiles: NormalizedPath[];
|
||||
const rootFiles: protocol.ExternalFile[] = [];
|
||||
for (const file of proj.rootFiles) {
|
||||
const normalized = toNormalizedPath(file.fileName);
|
||||
if (getBaseFileName(normalized) === "tsconfig.json") {
|
||||
(tsConfigFiles || (tsConfigFiles = [])).push(normalized);
|
||||
if (this.host.fileExists(normalized)) {
|
||||
(tsConfigFiles || (tsConfigFiles = [])).push(normalized);
|
||||
}
|
||||
}
|
||||
else {
|
||||
rootFiles.push(file);
|
||||
@@ -1234,8 +1410,15 @@ namespace ts.server {
|
||||
let exisingConfigFiles: string[];
|
||||
if (externalProject) {
|
||||
if (!tsConfigFiles) {
|
||||
const compilerOptions = convertCompilerOptions(proj.options);
|
||||
if (this.exceededTotalSizeLimitForNonTsFiles(compilerOptions, proj.rootFiles, externalFilePropertyReader)) {
|
||||
externalProject.disableLanguageService();
|
||||
}
|
||||
else {
|
||||
externalProject.enableLanguageService();
|
||||
}
|
||||
// external project already exists and not config files were added - update the project and return;
|
||||
this.updateNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, proj.options, proj.typingOptions, proj.options.compileOnSave, /*configFileErrors*/ undefined);
|
||||
this.updateNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, compilerOptions, proj.typeAcquisition, proj.options.compileOnSave, /*configFileErrors*/ undefined);
|
||||
return;
|
||||
}
|
||||
// some config files were added to external project (that previously were not there)
|
||||
@@ -1295,9 +1478,11 @@ namespace ts.server {
|
||||
else {
|
||||
// no config files - remove the item from the collection
|
||||
delete this.externalProjectToConfiguredProjectMap[proj.projectFileName];
|
||||
this.createAndAddExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typingOptions);
|
||||
this.createAndAddExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition);
|
||||
}
|
||||
if (!suppressRefreshOfInferredProjects) {
|
||||
this.refreshInferredProjects();
|
||||
}
|
||||
this.refreshInferredProjects();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,61 +3,67 @@
|
||||
/// <reference path="scriptInfo.ts" />
|
||||
|
||||
namespace ts.server {
|
||||
export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost, ServerLanguageServiceHost {
|
||||
export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost {
|
||||
private compilationSettings: ts.CompilerOptions;
|
||||
private readonly resolvedModuleNames: ts.FileMap<Map<ResolvedModuleWithFailedLookupLocations>>;
|
||||
private readonly resolvedTypeReferenceDirectives: ts.FileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>;
|
||||
private readonly resolvedModuleNames = createFileMap<Map<ResolvedModuleWithFailedLookupLocations>>();
|
||||
private readonly resolvedTypeReferenceDirectives = createFileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
|
||||
private readonly getCanonicalFileName: (fileName: string) => string;
|
||||
|
||||
private filesWithChangedSetOfUnresolvedImports: Path[];
|
||||
|
||||
private readonly resolveModuleName: typeof resolveModuleName;
|
||||
readonly trace: (s: string) => void;
|
||||
readonly realpath?: (path: string) => string;
|
||||
|
||||
constructor(private readonly host: ServerHost, private readonly project: Project, private readonly cancellationToken: HostCancellationToken) {
|
||||
this.getCanonicalFileName = ts.createGetCanonicalFileName(this.host.useCaseSensitiveFileNames);
|
||||
this.resolvedModuleNames = createFileMap<Map<ResolvedModuleWithFailedLookupLocations>>();
|
||||
this.resolvedTypeReferenceDirectives = createFileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
|
||||
|
||||
if (host.trace) {
|
||||
this.trace = s => host.trace(s);
|
||||
}
|
||||
|
||||
this.resolveModuleName = (moduleName, containingFile, compilerOptions, host) => {
|
||||
const globalCache = this.project.getTypeAcquisition().enable
|
||||
? this.project.projectService.typingsInstaller.globalTypingsCacheLocation
|
||||
: undefined;
|
||||
const primaryResult = resolveModuleName(moduleName, containingFile, compilerOptions, host);
|
||||
if (primaryResult.resolvedModule) {
|
||||
// return result immediately only if it is .ts, .tsx or .d.ts
|
||||
// return result immediately only if it is .ts, .tsx or .d.ts
|
||||
if (!(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension)) && globalCache !== undefined) {
|
||||
// otherwise try to load typings from @types
|
||||
if (fileExtensionIsAny(primaryResult.resolvedModule.resolvedFileName, supportedTypeScriptExtensions)) {
|
||||
return primaryResult;
|
||||
|
||||
// create different collection of failed lookup locations for second pass
|
||||
// if it will fail and we've already found something during the first pass - we don't want to pollute its results
|
||||
const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, this.project.getProjectName(), compilerOptions, host, globalCache);
|
||||
if (resolvedModule) {
|
||||
return { resolvedModule, failedLookupLocations: primaryResult.failedLookupLocations.concat(failedLookupLocations) };
|
||||
}
|
||||
}
|
||||
// create different collection of failed lookup locations for second pass
|
||||
// if it will fail and we've already found something during the first pass - we don't want to pollute its results
|
||||
const secondaryLookupFailedLookupLocations: string[] = [];
|
||||
const globalCache = this.project.projectService.typingsInstaller.globalTypingsCacheLocation;
|
||||
if (this.project.getTypingOptions().enableAutoDiscovery && globalCache) {
|
||||
const traceEnabled = isTraceEnabled(compilerOptions, host);
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, this.project.getProjectName(), moduleName, globalCache);
|
||||
}
|
||||
const state: ModuleResolutionState = { compilerOptions, host, skipTsx: false, traceEnabled };
|
||||
const resolvedName = loadModuleFromNodeModules(moduleName, globalCache, secondaryLookupFailedLookupLocations, state, /*checkOneLevel*/ true);
|
||||
if (resolvedName) {
|
||||
return createResolvedModule(resolvedName, /*isExternalLibraryImport*/ true, primaryResult.failedLookupLocations.concat(secondaryLookupFailedLookupLocations));
|
||||
}
|
||||
}
|
||||
if (!primaryResult.resolvedModule && secondaryLookupFailedLookupLocations.length) {
|
||||
primaryResult.failedLookupLocations = primaryResult.failedLookupLocations.concat(secondaryLookupFailedLookupLocations);
|
||||
}
|
||||
return primaryResult;
|
||||
};
|
||||
|
||||
if (this.host.realpath) {
|
||||
this.realpath = path => this.host.realpath(path);
|
||||
}
|
||||
}
|
||||
|
||||
private resolveNamesWithLocalCache<T extends { failedLookupLocations: string[] }, R extends { resolvedFileName?: string }>(
|
||||
public startRecordingFilesWithChangedResolutions() {
|
||||
this.filesWithChangedSetOfUnresolvedImports = [];
|
||||
}
|
||||
|
||||
public finishRecordingFilesWithChangedResolutions() {
|
||||
const collected = this.filesWithChangedSetOfUnresolvedImports;
|
||||
this.filesWithChangedSetOfUnresolvedImports = undefined;
|
||||
return collected;
|
||||
}
|
||||
|
||||
private resolveNamesWithLocalCache<T extends { failedLookupLocations: string[] }, R>(
|
||||
names: string[],
|
||||
containingFile: string,
|
||||
cache: ts.FileMap<Map<T>>,
|
||||
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T,
|
||||
getResult: (s: T) => R): R[] {
|
||||
getResult: (s: T) => R,
|
||||
getResultFileName: (result: R) => string | undefined,
|
||||
logChanges: boolean): R[] {
|
||||
|
||||
const path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName);
|
||||
const currentResolutionsInFile = cache.get(path);
|
||||
@@ -79,6 +85,11 @@ namespace ts.server {
|
||||
else {
|
||||
newResolutions[name] = resolution = loader(name, containingFile, compilerOptions, this);
|
||||
}
|
||||
if (logChanges && this.filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) {
|
||||
this.filesWithChangedSetOfUnresolvedImports.push(path);
|
||||
// reset log changes to avoid recording the same file multiple times
|
||||
logChanges = false;
|
||||
}
|
||||
}
|
||||
|
||||
ts.Debug.assert(resolution !== undefined);
|
||||
@@ -90,6 +101,24 @@ namespace ts.server {
|
||||
cache.set(path, newResolutions);
|
||||
return resolvedModules;
|
||||
|
||||
function resolutionIsEqualTo(oldResolution: T, newResolution: T): boolean {
|
||||
if (oldResolution === newResolution) {
|
||||
return true;
|
||||
}
|
||||
if (!oldResolution || !newResolution) {
|
||||
return false;
|
||||
}
|
||||
const oldResult = getResult(oldResolution);
|
||||
const newResult = getResult(newResolution);
|
||||
if (oldResult === newResult) {
|
||||
return true;
|
||||
}
|
||||
if (!oldResult || !newResult) {
|
||||
return false;
|
||||
}
|
||||
return getResultFileName(oldResult) === getResultFileName(newResult);
|
||||
}
|
||||
|
||||
function moduleResolutionIsValid(resolution: T): boolean {
|
||||
if (!resolution) {
|
||||
return false;
|
||||
@@ -97,10 +126,7 @@ namespace ts.server {
|
||||
|
||||
const result = getResult(resolution);
|
||||
if (result) {
|
||||
if (result.resolvedFileName && result.resolvedFileName === lastDeletedFileName) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return getResultFileName(result) !== lastDeletedFileName;
|
||||
}
|
||||
|
||||
// consider situation if we have no candidate locations as valid resolution.
|
||||
@@ -109,6 +135,10 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
getNewLine() {
|
||||
return this.host.newLine;
|
||||
}
|
||||
|
||||
getProjectVersion() {
|
||||
return this.project.getProjectVersion();
|
||||
}
|
||||
@@ -126,11 +156,13 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
|
||||
return this.resolveNamesWithLocalCache(typeDirectiveNames, containingFile, this.resolvedTypeReferenceDirectives, resolveTypeReferenceDirective, m => m.resolvedTypeReferenceDirective);
|
||||
return this.resolveNamesWithLocalCache(typeDirectiveNames, containingFile, this.resolvedTypeReferenceDirectives, resolveTypeReferenceDirective,
|
||||
m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName, /*logChanges*/ false);
|
||||
}
|
||||
|
||||
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModule[] {
|
||||
return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, this.resolveModuleName, m => m.resolvedModule);
|
||||
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModuleFull[] {
|
||||
return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, this.resolveModuleName,
|
||||
m => m.resolvedModule, r => r.resolvedFileName, /*logChanges*/ true);
|
||||
}
|
||||
|
||||
getDefaultLibFileName() {
|
||||
@@ -141,7 +173,7 @@ namespace ts.server {
|
||||
getScriptSnapshot(filename: string): ts.IScriptSnapshot {
|
||||
const scriptInfo = this.project.getScriptInfoLSHost(filename);
|
||||
if (scriptInfo) {
|
||||
return scriptInfo.snap();
|
||||
return scriptInfo.getSnapshot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,10 +229,11 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
setCompilationSettings(opt: ts.CompilerOptions) {
|
||||
if (changesAffectModuleResolution(this.compilationSettings, opt)) {
|
||||
this.resolvedModuleNames.clear();
|
||||
this.resolvedTypeReferenceDirectives.clear();
|
||||
}
|
||||
this.compilationSettings = opt;
|
||||
// conservatively assume that changing compiler options might affect module resolution strategy
|
||||
this.resolvedModuleNames.clear();
|
||||
this.resolvedTypeReferenceDirectives.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/// <reference path="..\services\services.ts" />
|
||||
/// <reference path="..\services\services.ts" />
|
||||
/// <reference path="utilities.ts"/>
|
||||
/// <reference path="scriptInfo.ts"/>
|
||||
/// <reference path="lsHost.ts"/>
|
||||
@@ -62,14 +62,52 @@ namespace ts.server {
|
||||
projectErrors: Diagnostic[];
|
||||
}
|
||||
|
||||
export class UnresolvedImportsMap {
|
||||
readonly perFileMap = createFileMap<ReadonlyArray<string>>();
|
||||
private version = 0;
|
||||
|
||||
public clear() {
|
||||
this.perFileMap.clear();
|
||||
this.version = 0;
|
||||
}
|
||||
|
||||
public getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public remove(path: Path) {
|
||||
this.perFileMap.remove(path);
|
||||
this.version++;
|
||||
}
|
||||
|
||||
public get(path: Path) {
|
||||
return this.perFileMap.get(path);
|
||||
}
|
||||
|
||||
public set(path: Path, value: ReadonlyArray<string>) {
|
||||
this.perFileMap.set(path, value);
|
||||
this.version++;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class Project {
|
||||
private rootFiles: ScriptInfo[] = [];
|
||||
private rootFilesMap: FileMap<ScriptInfo> = createFileMap<ScriptInfo>();
|
||||
private lsHost: ServerLanguageServiceHost;
|
||||
private lsHost: LSHost;
|
||||
private program: ts.Program;
|
||||
|
||||
private languageService: LanguageService;
|
||||
private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap();
|
||||
private lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
|
||||
|
||||
private readonly languageService: LanguageService;
|
||||
|
||||
public languageServiceEnabled = true;
|
||||
|
||||
builder: Builder;
|
||||
/**
|
||||
* Set of files names that were updated since the last call to getChangesSinceVersion.
|
||||
*/
|
||||
private updatedFileNames: Map<string>;
|
||||
/**
|
||||
* Set of files that was returned from the last call to getChangesSinceVersion.
|
||||
*/
|
||||
@@ -91,7 +129,7 @@ namespace ts.server {
|
||||
*/
|
||||
private projectStateVersion = 0;
|
||||
|
||||
private typingFiles: TypingsArray;
|
||||
private typingFiles: SortedReadonlyArray<string>;
|
||||
|
||||
protected projectErrors: Diagnostic[];
|
||||
|
||||
@@ -107,12 +145,17 @@ namespace ts.server {
|
||||
return hasOneOrMoreJsAndNoTsFiles(this);
|
||||
}
|
||||
|
||||
public getCachedUnresolvedImportsPerFile_TestOnly() {
|
||||
return this.cachedUnresolvedImportsPerFile;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly projectName: string,
|
||||
readonly projectKind: ProjectKind,
|
||||
readonly projectService: ProjectService,
|
||||
private documentRegistry: ts.DocumentRegistry,
|
||||
hasExplicitListOfFiles: boolean,
|
||||
public languageServiceEnabled: boolean,
|
||||
languageServiceEnabled: boolean,
|
||||
private compilerOptions: CompilerOptions,
|
||||
public compileOnSaveEnabled: boolean) {
|
||||
|
||||
@@ -133,10 +176,14 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
if (languageServiceEnabled) {
|
||||
this.enableLanguageService();
|
||||
}
|
||||
else {
|
||||
this.setInternalCompilerOptionsForEmittingJsFiles();
|
||||
|
||||
this.lsHost = new LSHost(this.projectService.host, this, this.projectService.cancellationToken);
|
||||
this.lsHost.setCompilationSettings(this.compilerOptions);
|
||||
|
||||
this.languageService = ts.createLanguageService(this.lsHost, this.documentRegistry);
|
||||
|
||||
if (!languageServiceEnabled) {
|
||||
this.disableLanguageService();
|
||||
}
|
||||
|
||||
@@ -144,6 +191,12 @@ namespace ts.server {
|
||||
this.markAsDirty();
|
||||
}
|
||||
|
||||
private setInternalCompilerOptionsForEmittingJsFiles() {
|
||||
if (this.projectKind === ProjectKind.Inferred || this.projectKind === ProjectKind.External) {
|
||||
this.compilerOptions.noEmitForJsFiles = true;
|
||||
}
|
||||
}
|
||||
|
||||
getProjectErrors() {
|
||||
return this.projectErrors;
|
||||
}
|
||||
@@ -168,23 +221,27 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
enableLanguageService() {
|
||||
const lsHost = new LSHost(this.projectService.host, this, this.projectService.cancellationToken);
|
||||
lsHost.setCompilationSettings(this.compilerOptions);
|
||||
this.languageService = ts.createLanguageService(lsHost, this.documentRegistry);
|
||||
|
||||
this.lsHost = lsHost;
|
||||
if (this.languageServiceEnabled) {
|
||||
return;
|
||||
}
|
||||
this.languageServiceEnabled = true;
|
||||
this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ true);
|
||||
}
|
||||
|
||||
disableLanguageService() {
|
||||
this.languageService = nullLanguageService;
|
||||
this.lsHost = nullLanguageServiceHost;
|
||||
if (!this.languageServiceEnabled) {
|
||||
return;
|
||||
}
|
||||
this.languageService.cleanupSemanticCache();
|
||||
this.languageServiceEnabled = false;
|
||||
this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false);
|
||||
}
|
||||
|
||||
abstract getProjectName(): string;
|
||||
getProjectName() {
|
||||
return this.projectName;
|
||||
}
|
||||
abstract getProjectRootPath(): string | undefined;
|
||||
abstract getTypingOptions(): TypingOptions;
|
||||
abstract getTypeAcquisition(): TypeAcquisition;
|
||||
|
||||
getSourceFile(path: Path) {
|
||||
if (!this.program) {
|
||||
@@ -207,8 +264,9 @@ namespace ts.server {
|
||||
info.detachFromProject(this);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// release all root files
|
||||
if (!this.program || !this.languageServiceEnabled) {
|
||||
// release all root files either if there is no program or language service is disabled.
|
||||
// in the latter case set of root files can be larger than the set of files in program.
|
||||
for (const root of this.rootFiles) {
|
||||
root.detachFromProject(this);
|
||||
}
|
||||
@@ -237,7 +295,10 @@ namespace ts.server {
|
||||
const result: string[] = [];
|
||||
if (this.rootFiles) {
|
||||
for (const f of this.rootFiles) {
|
||||
result.push(f.fileName);
|
||||
if (this.languageServiceEnabled || f.isScriptOpen()) {
|
||||
// if language service is disabled - process only files that are open
|
||||
result.push(f.fileName);
|
||||
}
|
||||
}
|
||||
if (this.typingFiles) {
|
||||
for (const f of this.typingFiles) {
|
||||
@@ -253,6 +314,10 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getScriptInfos() {
|
||||
if (!this.languageServiceEnabled) {
|
||||
// if language service is not enabled - return just root files
|
||||
return this.rootFiles;
|
||||
}
|
||||
return map(this.program.getSourceFiles(), sourceFile => {
|
||||
const scriptInfo = this.projectService.getScriptInfoForPath(sourceFile.path);
|
||||
if (!scriptInfo) {
|
||||
@@ -269,7 +334,7 @@ namespace ts.server {
|
||||
return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles);
|
||||
}
|
||||
|
||||
getFileNames() {
|
||||
getFileNames(excludeFilesFromExternalLibraries?: boolean) {
|
||||
if (!this.program) {
|
||||
return [];
|
||||
}
|
||||
@@ -285,8 +350,14 @@ namespace ts.server {
|
||||
}
|
||||
return rootFiles;
|
||||
}
|
||||
const sourceFiles = this.program.getSourceFiles();
|
||||
return sourceFiles.map(sourceFile => asNormalizedPath(sourceFile.fileName));
|
||||
const result: NormalizedPath[] = [];
|
||||
for (const f of this.program.getSourceFiles()) {
|
||||
if (excludeFilesFromExternalLibraries && this.program.isSourceFileFromExternalLibrary(f)) {
|
||||
continue;
|
||||
}
|
||||
result.push(asNormalizedPath(f.fileName));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getAllEmittableFiles() {
|
||||
@@ -310,7 +381,7 @@ namespace ts.server {
|
||||
|
||||
containsFile(filename: NormalizedPath, requireOpen?: boolean) {
|
||||
const info = this.projectService.getScriptInfoForNormalizedPath(filename);
|
||||
if (info && (info.isOpen || !requireOpen)) {
|
||||
if (info && (info.isScriptOpen() || !requireOpen)) {
|
||||
return this.containsScriptInfo(info);
|
||||
}
|
||||
}
|
||||
@@ -333,6 +404,7 @@ namespace ts.server {
|
||||
removeFile(info: ScriptInfo, detachFromProject = true) {
|
||||
this.removeRootFileIfNecessary(info);
|
||||
this.lsHost.notifyFileRemoved(info);
|
||||
this.cachedUnresolvedImportsPerFile.remove(info.path);
|
||||
|
||||
if (detachFromProject) {
|
||||
info.detachFromProject(this);
|
||||
@@ -341,30 +413,99 @@ namespace ts.server {
|
||||
this.markAsDirty();
|
||||
}
|
||||
|
||||
registerFileUpdate(fileName: string) {
|
||||
(this.updatedFileNames || (this.updatedFileNames = createMap<string>()))[fileName] = fileName;
|
||||
}
|
||||
|
||||
markAsDirty() {
|
||||
this.projectStateVersion++;
|
||||
}
|
||||
|
||||
private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: string[]) {
|
||||
const cached = this.cachedUnresolvedImportsPerFile.get(file.path);
|
||||
if (cached) {
|
||||
// found cached result - use it and return
|
||||
for (const f of cached) {
|
||||
result.push(f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let unresolvedImports: string[];
|
||||
if (file.resolvedModules) {
|
||||
for (const name in file.resolvedModules) {
|
||||
// pick unresolved non-relative names
|
||||
if (!file.resolvedModules[name] && !isExternalModuleNameRelative(name)) {
|
||||
// for non-scoped names extract part up-to the first slash
|
||||
// for scoped names - extract up to the second slash
|
||||
let trimmed = name.trim();
|
||||
let i = trimmed.indexOf("/");
|
||||
if (i !== -1 && trimmed.charCodeAt(0) === CharacterCodes.at) {
|
||||
i = trimmed.indexOf("/", i + 1);
|
||||
}
|
||||
if (i !== -1) {
|
||||
trimmed = trimmed.substr(0, i);
|
||||
}
|
||||
(unresolvedImports || (unresolvedImports = [])).push(trimmed);
|
||||
result.push(trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports || emptyArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates set of files that contribute to this project
|
||||
* @returns: true if set of files in the project stays the same and false - otherwise.
|
||||
*/
|
||||
updateGraph(): boolean {
|
||||
if (!this.languageServiceEnabled) {
|
||||
return true;
|
||||
}
|
||||
this.lsHost.startRecordingFilesWithChangedResolutions();
|
||||
|
||||
let hasChanges = this.updateGraphWorker();
|
||||
const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, hasChanges);
|
||||
|
||||
const changedFiles: ReadonlyArray<Path> = this.lsHost.finishRecordingFilesWithChangedResolutions() || emptyArray;
|
||||
|
||||
for (const file of changedFiles) {
|
||||
// delete cached information for changed files
|
||||
this.cachedUnresolvedImportsPerFile.remove(file);
|
||||
}
|
||||
|
||||
// 1. no changes in structure, no changes in unresolved imports - do nothing
|
||||
// 2. no changes in structure, unresolved imports were changed - collect unresolved imports for all files
|
||||
// (can reuse cached imports for files that were not changed)
|
||||
// 3. new files were added/removed, but compilation settings stays the same - collect unresolved imports for all new/modified files
|
||||
// (can reuse cached imports for files that were not changed)
|
||||
// 4. compilation settings were changed in the way that might affect module resolution - drop all caches and collect all data from the scratch
|
||||
let unresolvedImports: SortedReadonlyArray<string>;
|
||||
if (hasChanges || changedFiles.length) {
|
||||
const result: string[] = [];
|
||||
for (const sourceFile of this.program.getSourceFiles()) {
|
||||
this.extractUnresolvedImportsFromSourceFile(sourceFile, result);
|
||||
}
|
||||
this.lastCachedUnresolvedImportsList = toSortedReadonlyArray(result);
|
||||
}
|
||||
unresolvedImports = this.lastCachedUnresolvedImportsList;
|
||||
|
||||
const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, unresolvedImports, hasChanges);
|
||||
if (this.setTypings(cachedTypings)) {
|
||||
hasChanges = this.updateGraphWorker() || hasChanges;
|
||||
}
|
||||
|
||||
// update builder only if language service is enabled
|
||||
// otherwise tell it to drop its internal state
|
||||
if (this.languageServiceEnabled) {
|
||||
this.builder.onProjectUpdateGraph();
|
||||
}
|
||||
else {
|
||||
this.builder.clear();
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
this.projectStructureVersion++;
|
||||
}
|
||||
return !hasChanges;
|
||||
}
|
||||
|
||||
private setTypings(typings: TypingsArray): boolean {
|
||||
private setTypings(typings: SortedReadonlyArray<string>): boolean {
|
||||
if (arrayIsEqualTo(this.typingFiles, typings)) {
|
||||
return false;
|
||||
}
|
||||
@@ -396,7 +537,6 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.builder.onProjectUpdateGraph();
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
@@ -437,18 +577,24 @@ namespace ts.server {
|
||||
compilerOptions.allowJs = true;
|
||||
}
|
||||
compilerOptions.allowNonTsExtensions = true;
|
||||
if (changesAffectModuleResolution(this.compilerOptions, compilerOptions)) {
|
||||
// reset cached unresolved imports if changes in compiler options affected module resolution
|
||||
this.cachedUnresolvedImportsPerFile.clear();
|
||||
this.lastCachedUnresolvedImportsList = undefined;
|
||||
}
|
||||
this.compilerOptions = compilerOptions;
|
||||
this.setInternalCompilerOptionsForEmittingJsFiles();
|
||||
this.lsHost.setCompilationSettings(compilerOptions);
|
||||
|
||||
this.markAsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
reloadScript(filename: NormalizedPath): boolean {
|
||||
reloadScript(filename: NormalizedPath, tempFileName?: NormalizedPath): boolean {
|
||||
const script = this.projectService.getScriptInfoForNormalizedPath(filename);
|
||||
if (script) {
|
||||
Debug.assert(script.isAttached(this));
|
||||
script.reloadFromFile();
|
||||
script.reloadFromFile(tempFileName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -461,12 +607,15 @@ namespace ts.server {
|
||||
projectName: this.getProjectName(),
|
||||
version: this.projectStructureVersion,
|
||||
isInferred: this.projectKind === ProjectKind.Inferred,
|
||||
options: this.getCompilerOptions()
|
||||
options: this.getCompilerOptions(),
|
||||
languageServiceDisabled: !this.languageServiceEnabled
|
||||
};
|
||||
const updatedFileNames = this.updatedFileNames;
|
||||
this.updatedFileNames = undefined;
|
||||
// check if requested version is the same that we have reported last time
|
||||
if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) {
|
||||
// if current structure version is the same - return info witout any changes
|
||||
if (this.projectStructureVersion == this.lastReportedVersion) {
|
||||
// if current structure version is the same - return info without any changes
|
||||
if (this.projectStructureVersion == this.lastReportedVersion && !updatedFileNames) {
|
||||
return { info, projectErrors: this.projectErrors };
|
||||
}
|
||||
// compute and return the difference
|
||||
@@ -475,6 +624,7 @@ namespace ts.server {
|
||||
|
||||
const added: string[] = [];
|
||||
const removed: string[] = [];
|
||||
const updated: string[] = getOwnKeys(updatedFileNames);
|
||||
for (const id in currentFiles) {
|
||||
if (!hasProperty(lastReportedFileNames, id)) {
|
||||
added.push(id);
|
||||
@@ -487,7 +637,7 @@ namespace ts.server {
|
||||
}
|
||||
this.lastReportedFileNames = currentFiles;
|
||||
this.lastReportedVersion = this.projectStructureVersion;
|
||||
return { info, changes: { added, removed }, projectErrors: this.projectErrors };
|
||||
return { info, changes: { added, removed, updated }, projectErrors: this.projectErrors };
|
||||
}
|
||||
else {
|
||||
// unknown version - return everything
|
||||
@@ -563,31 +713,27 @@ namespace ts.server {
|
||||
|
||||
export class InferredProject extends Project {
|
||||
|
||||
private static NextId = 1;
|
||||
|
||||
/**
|
||||
* Unique name that identifies this particular inferred project
|
||||
*/
|
||||
private readonly inferredProjectName: string;
|
||||
private static newName = (() => {
|
||||
let nextId = 1;
|
||||
return () => {
|
||||
const id = nextId;
|
||||
nextId++;
|
||||
return makeInferredProjectName(id);
|
||||
}
|
||||
})();
|
||||
|
||||
// Used to keep track of what directories are watched for this project
|
||||
directoriesWatchedForTsconfig: string[] = [];
|
||||
|
||||
constructor(projectService: ProjectService, documentRegistry: ts.DocumentRegistry, languageServiceEnabled: boolean, compilerOptions: CompilerOptions, public compileOnSaveEnabled: boolean) {
|
||||
super(ProjectKind.Inferred,
|
||||
constructor(projectService: ProjectService, documentRegistry: ts.DocumentRegistry, compilerOptions: CompilerOptions) {
|
||||
super(InferredProject.newName(),
|
||||
ProjectKind.Inferred,
|
||||
projectService,
|
||||
documentRegistry,
|
||||
/*files*/ undefined,
|
||||
languageServiceEnabled,
|
||||
/*languageServiceEnabled*/ true,
|
||||
compilerOptions,
|
||||
compileOnSaveEnabled);
|
||||
|
||||
this.inferredProjectName = makeInferredProjectName(InferredProject.NextId);
|
||||
InferredProject.NextId++;
|
||||
}
|
||||
|
||||
getProjectName() {
|
||||
return this.inferredProjectName;
|
||||
/*compileOnSaveEnabled*/ false);
|
||||
}
|
||||
|
||||
getProjectRootPath() {
|
||||
@@ -607,9 +753,9 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
getTypingOptions(): TypingOptions {
|
||||
getTypeAcquisition(): TypeAcquisition {
|
||||
return {
|
||||
enableAutoDiscovery: allRootFilesAreJsOrDts(this),
|
||||
enable: allRootFilesAreJsOrDts(this),
|
||||
include: [],
|
||||
exclude: []
|
||||
};
|
||||
@@ -617,16 +763,17 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
export class ConfiguredProject extends Project {
|
||||
private typingOptions: TypingOptions;
|
||||
private typeAcquisition: TypeAcquisition;
|
||||
private projectFileWatcher: FileWatcher;
|
||||
private directoryWatcher: FileWatcher;
|
||||
private directoriesWatchedForWildcards: Map<FileWatcher>;
|
||||
private typeRootsWatchers: FileWatcher[];
|
||||
readonly canonicalConfigFilePath: NormalizedPath;
|
||||
|
||||
/** Used for configured projects which may have multiple open roots */
|
||||
openRefCount = 0;
|
||||
|
||||
constructor(readonly configFileName: NormalizedPath,
|
||||
constructor(configFileName: NormalizedPath,
|
||||
projectService: ProjectService,
|
||||
documentRegistry: ts.DocumentRegistry,
|
||||
hasExplicitListOfFiles: boolean,
|
||||
@@ -634,31 +781,32 @@ namespace ts.server {
|
||||
private wildcardDirectories: Map<WatchDirectoryFlags>,
|
||||
languageServiceEnabled: boolean,
|
||||
public compileOnSaveEnabled: boolean) {
|
||||
super(ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled);
|
||||
super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled);
|
||||
this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName));
|
||||
}
|
||||
|
||||
getConfigFilePath() {
|
||||
return this.getProjectName();
|
||||
}
|
||||
|
||||
getProjectRootPath() {
|
||||
return getDirectoryPath(this.configFileName);
|
||||
return getDirectoryPath(this.getConfigFilePath());
|
||||
}
|
||||
|
||||
setProjectErrors(projectErrors: Diagnostic[]) {
|
||||
this.projectErrors = projectErrors;
|
||||
}
|
||||
|
||||
setTypingOptions(newTypingOptions: TypingOptions): void {
|
||||
this.typingOptions = newTypingOptions;
|
||||
setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void {
|
||||
this.typeAcquisition = newTypeAcquisition;
|
||||
}
|
||||
|
||||
getTypingOptions() {
|
||||
return this.typingOptions;
|
||||
}
|
||||
|
||||
getProjectName() {
|
||||
return this.configFileName;
|
||||
getTypeAcquisition() {
|
||||
return this.typeAcquisition;
|
||||
}
|
||||
|
||||
watchConfigFile(callback: (project: ConfiguredProject) => void) {
|
||||
this.projectFileWatcher = this.projectService.host.watchFile(this.configFileName, _ => callback(this));
|
||||
this.projectFileWatcher = this.projectService.host.watchFile(this.getConfigFilePath(), _ => callback(this));
|
||||
}
|
||||
|
||||
watchTypeRoots(callback: (project: ConfiguredProject, path: string) => void) {
|
||||
@@ -676,7 +824,7 @@ namespace ts.server {
|
||||
return;
|
||||
}
|
||||
|
||||
const directoryToWatch = getDirectoryPath(this.configFileName);
|
||||
const directoryToWatch = getDirectoryPath(this.getConfigFilePath());
|
||||
this.projectService.logger.info(`Add recursive watcher for: ${directoryToWatch}`);
|
||||
this.directoryWatcher = this.projectService.host.watchDirectory(directoryToWatch, path => callback(this, path), /*recursive*/ true);
|
||||
}
|
||||
@@ -685,7 +833,7 @@ namespace ts.server {
|
||||
if (!this.wildcardDirectories) {
|
||||
return;
|
||||
}
|
||||
const configDirectoryPath = getDirectoryPath(this.configFileName);
|
||||
const configDirectoryPath = getDirectoryPath(this.getConfigFilePath());
|
||||
this.directoriesWatchedForWildcards = reduceProperties(this.wildcardDirectories, (watchers, flag, directory) => {
|
||||
if (comparePaths(configDirectoryPath, directory, ".", !this.projectService.host.useCaseSensitiveFileNames) !== Comparison.EqualTo) {
|
||||
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
|
||||
@@ -744,15 +892,15 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
export class ExternalProject extends Project {
|
||||
private typingOptions: TypingOptions;
|
||||
constructor(readonly externalProjectName: string,
|
||||
private typeAcquisition: TypeAcquisition;
|
||||
constructor(externalProjectName: string,
|
||||
projectService: ProjectService,
|
||||
documentRegistry: ts.DocumentRegistry,
|
||||
compilerOptions: CompilerOptions,
|
||||
languageServiceEnabled: boolean,
|
||||
public compileOnSaveEnabled: boolean,
|
||||
private readonly projectFilePath?: string) {
|
||||
super(ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions, compileOnSaveEnabled);
|
||||
super(externalProjectName, ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions, compileOnSaveEnabled);
|
||||
}
|
||||
|
||||
getProjectRootPath() {
|
||||
@@ -762,43 +910,39 @@ namespace ts.server {
|
||||
// if the projectFilePath is not given, we make the assumption that the project name
|
||||
// is the path of the project file. AS the project name is provided by VS, we need to
|
||||
// normalize slashes before using it as a file name.
|
||||
return getDirectoryPath(normalizeSlashes(this.externalProjectName));
|
||||
return getDirectoryPath(normalizeSlashes(this.getProjectName()));
|
||||
}
|
||||
|
||||
getTypingOptions() {
|
||||
return this.typingOptions;
|
||||
getTypeAcquisition() {
|
||||
return this.typeAcquisition;
|
||||
}
|
||||
|
||||
setProjectErrors(projectErrors: Diagnostic[]) {
|
||||
this.projectErrors = projectErrors;
|
||||
}
|
||||
|
||||
setTypingOptions(newTypingOptions: TypingOptions): void {
|
||||
if (!newTypingOptions) {
|
||||
setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void {
|
||||
if (!newTypeAcquisition) {
|
||||
// set default typings options
|
||||
newTypingOptions = {
|
||||
enableAutoDiscovery: allRootFilesAreJsOrDts(this),
|
||||
newTypeAcquisition = {
|
||||
enable: allRootFilesAreJsOrDts(this),
|
||||
include: [],
|
||||
exclude: []
|
||||
};
|
||||
}
|
||||
else {
|
||||
if (newTypingOptions.enableAutoDiscovery === undefined) {
|
||||
if (newTypeAcquisition.enable === undefined) {
|
||||
// if autoDiscovery was not specified by the caller - set it based on the content of the project
|
||||
newTypingOptions.enableAutoDiscovery = allRootFilesAreJsOrDts(this);
|
||||
newTypeAcquisition.enable = allRootFilesAreJsOrDts(this);
|
||||
}
|
||||
if (!newTypingOptions.include) {
|
||||
newTypingOptions.include = [];
|
||||
if (!newTypeAcquisition.include) {
|
||||
newTypeAcquisition.include = [];
|
||||
}
|
||||
if (!newTypingOptions.exclude) {
|
||||
newTypingOptions.exclude = [];
|
||||
if (!newTypeAcquisition.exclude) {
|
||||
newTypeAcquisition.exclude = [];
|
||||
}
|
||||
}
|
||||
this.typingOptions = newTypingOptions;
|
||||
}
|
||||
|
||||
getProjectName() {
|
||||
return this.externalProjectName;
|
||||
this.typeAcquisition = newTypeAcquisition;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/**
|
||||
* Declaration module describing the TypeScript Server protocol
|
||||
*/
|
||||
namespace ts.server.protocol {
|
||||
@@ -109,7 +109,7 @@ namespace ts.server.protocol {
|
||||
/**
|
||||
* One of "request", "response", or "event"
|
||||
*/
|
||||
type: string;
|
||||
type: "request" | "response" | "event";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -833,7 +833,7 @@ namespace ts.server.protocol {
|
||||
/**
|
||||
* Script kind of the file
|
||||
*/
|
||||
scriptKind?: ScriptKind;
|
||||
scriptKind?: ScriptKindName | ts.ScriptKind;
|
||||
/**
|
||||
* Whether file has mixed content (i.e. .cshtml file that combines html markup with C#/JavaScript)
|
||||
*/
|
||||
@@ -861,25 +861,32 @@ namespace ts.server.protocol {
|
||||
*/
|
||||
options: ExternalProjectCompilerOptions;
|
||||
/**
|
||||
* Explicitly specified typing options for the project
|
||||
* @deprecated typingOptions. Use typeAcquisition instead
|
||||
*/
|
||||
typingOptions?: TypingOptions;
|
||||
typingOptions?: TypeAcquisition;
|
||||
/**
|
||||
* Explicitly specified type acquisition for the project
|
||||
*/
|
||||
typeAcquisition?: TypeAcquisition;
|
||||
}
|
||||
|
||||
/**
|
||||
* For external projects, some of the project settings are sent together with
|
||||
* compiler settings.
|
||||
*/
|
||||
export interface ExternalProjectCompilerOptions extends CompilerOptions {
|
||||
export interface CompileOnSaveMixin {
|
||||
/**
|
||||
* If compile on save is enabled for the project
|
||||
*/
|
||||
compileOnSave?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* For external projects, some of the project settings are sent together with
|
||||
* compiler settings.
|
||||
*/
|
||||
export type ExternalProjectCompilerOptions = CompilerOptions & CompileOnSaveMixin;
|
||||
|
||||
/**
|
||||
* Contains information about current project version
|
||||
*/
|
||||
/* @internal */
|
||||
export interface ProjectVersionInfo {
|
||||
/**
|
||||
* Project name
|
||||
@@ -896,7 +903,12 @@ namespace ts.server.protocol {
|
||||
/**
|
||||
* Current set of compiler options for project
|
||||
*/
|
||||
options: CompilerOptions;
|
||||
options: ts.CompilerOptions;
|
||||
|
||||
/**
|
||||
* true if project language service is disabled
|
||||
*/
|
||||
languageServiceDisabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -911,6 +923,10 @@ namespace ts.server.protocol {
|
||||
* List of removed files
|
||||
*/
|
||||
removed: string[];
|
||||
/**
|
||||
* List of updated files
|
||||
*/
|
||||
updated: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -920,6 +936,7 @@ namespace ts.server.protocol {
|
||||
* if changes is set - then this is the set of changes that should be applied to existing project
|
||||
* otherwise - assume that nothing is changed
|
||||
*/
|
||||
/* @internal */
|
||||
export interface ProjectFiles {
|
||||
/**
|
||||
* Information abount project verison
|
||||
@@ -938,6 +955,7 @@ namespace ts.server.protocol {
|
||||
/**
|
||||
* Combines project information with project level errors.
|
||||
*/
|
||||
/* @internal */
|
||||
export interface ProjectFilesWithDiagnostics extends ProjectFiles {
|
||||
/**
|
||||
* List of errors in project
|
||||
@@ -981,6 +999,11 @@ namespace ts.server.protocol {
|
||||
* The format options to use during formatting and other code editing features.
|
||||
*/
|
||||
formatOptions?: FormatCodeSettings;
|
||||
|
||||
/**
|
||||
* The host's additional supported file extensions
|
||||
*/
|
||||
extraFileExtensions?: FileExtensionInfo[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1012,9 +1035,11 @@ namespace ts.server.protocol {
|
||||
* Used to specify the script kind of the file explicitly. It could be one of the following:
|
||||
* "TS", "JS", "TSX", "JSX"
|
||||
*/
|
||||
scriptKindName?: "TS" | "JS" | "TSX" | "JSX";
|
||||
scriptKindName?: ScriptKindName;
|
||||
}
|
||||
|
||||
export type ScriptKindName = "TS" | "JS" | "TSX" | "JSX";
|
||||
|
||||
/**
|
||||
* Open request; value of command field is "open". Notify the
|
||||
* server that the client has file open. The server will not
|
||||
@@ -1109,6 +1134,7 @@ namespace ts.server.protocol {
|
||||
/**
|
||||
* Arguments to SynchronizeProjectListRequest
|
||||
*/
|
||||
/* @internal */
|
||||
export interface SynchronizeProjectListRequestArgs {
|
||||
/**
|
||||
* List of last known projects
|
||||
@@ -1806,6 +1832,27 @@ namespace ts.server.protocol {
|
||||
event: "configFileDiag";
|
||||
}
|
||||
|
||||
export type ProjectLanguageServiceStateEventName = "projectLanguageServiceState";
|
||||
export interface ProjectLanguageServiceStateEvent extends Event {
|
||||
event: ProjectLanguageServiceStateEventName;
|
||||
body?: ProjectLanguageServiceStateEventBody;
|
||||
}
|
||||
|
||||
export interface ProjectLanguageServiceStateEventBody {
|
||||
/**
|
||||
* Project name that has changes in the state of language service.
|
||||
* For configured projects this will be the config file path.
|
||||
* For external projects this will be the name of the projects specified when project was open.
|
||||
* For inferred projects this event is not raised.
|
||||
*/
|
||||
projectName: string;
|
||||
/**
|
||||
* True if language service state switched from disabled to enabled
|
||||
* and false otherwise.
|
||||
*/
|
||||
languageServiceEnabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arguments for reload request.
|
||||
*/
|
||||
@@ -2049,6 +2096,75 @@ namespace ts.server.protocol {
|
||||
childItems?: NavigationTree[];
|
||||
}
|
||||
|
||||
export type TelemetryEventName = "telemetry";
|
||||
|
||||
export interface TelemetryEvent extends Event {
|
||||
event: TelemetryEventName;
|
||||
body: TelemetryEventBody;
|
||||
}
|
||||
|
||||
export interface TelemetryEventBody {
|
||||
telemetryEventName: string;
|
||||
payload: any;
|
||||
}
|
||||
|
||||
export type TypingsInstalledTelemetryEventName = "typingsInstalled";
|
||||
|
||||
export interface TypingsInstalledTelemetryEventBody extends TelemetryEventBody {
|
||||
telemetryEventName: TypingsInstalledTelemetryEventName;
|
||||
payload: TypingsInstalledTelemetryEventPayload;
|
||||
}
|
||||
|
||||
export interface TypingsInstalledTelemetryEventPayload {
|
||||
/**
|
||||
* Comma separated list of installed typing packages
|
||||
*/
|
||||
installedPackages: string;
|
||||
/**
|
||||
* true if install request succeeded, otherwise - false
|
||||
*/
|
||||
installSuccess: boolean;
|
||||
|
||||
/**
|
||||
* version of typings installer
|
||||
*/
|
||||
typingsInstallerVersion: string;
|
||||
}
|
||||
|
||||
export type BeginInstallTypesEventName = "beginInstallTypes";
|
||||
export type EndInstallTypesEventName = "endInstallTypes";
|
||||
|
||||
export interface BeginInstallTypesEvent extends Event {
|
||||
event: BeginInstallTypesEventName;
|
||||
body: BeginInstallTypesEventBody;
|
||||
}
|
||||
|
||||
export interface EndInstallTypesEvent extends Event {
|
||||
event: EndInstallTypesEventName;
|
||||
body: EndInstallTypesEventBody;
|
||||
}
|
||||
|
||||
export interface InstallTypesEventBody {
|
||||
/**
|
||||
* correlation id to match begin and end events
|
||||
*/
|
||||
eventId: number;
|
||||
/**
|
||||
* list of packages to install
|
||||
*/
|
||||
packages: ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
export interface BeginInstallTypesEventBody extends InstallTypesEventBody {
|
||||
}
|
||||
|
||||
export interface EndInstallTypesEventBody extends InstallTypesEventBody {
|
||||
/**
|
||||
* true if installation succeeded, otherwise false
|
||||
*/
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface NavBarResponse extends Response {
|
||||
body?: NavigationBarItem[];
|
||||
}
|
||||
@@ -2056,4 +2172,143 @@ namespace ts.server.protocol {
|
||||
export interface NavTreeResponse extends Response {
|
||||
body?: NavigationTree;
|
||||
}
|
||||
|
||||
export namespace IndentStyle {
|
||||
export type None = "None";
|
||||
export type Block = "Block";
|
||||
export type Smart = "Smart";
|
||||
}
|
||||
|
||||
export type IndentStyle = IndentStyle.None | IndentStyle.Block | IndentStyle.Smart;
|
||||
|
||||
export interface EditorSettings {
|
||||
baseIndentSize?: number;
|
||||
indentSize?: number;
|
||||
tabSize?: number;
|
||||
newLineCharacter?: string;
|
||||
convertTabsToSpaces?: boolean;
|
||||
indentStyle?: IndentStyle | ts.IndentStyle;
|
||||
}
|
||||
|
||||
export interface FormatCodeSettings extends EditorSettings {
|
||||
insertSpaceAfterCommaDelimiter?: boolean;
|
||||
insertSpaceAfterSemicolonInForStatements?: boolean;
|
||||
insertSpaceBeforeAndAfterBinaryOperators?: boolean;
|
||||
insertSpaceAfterConstructor?: boolean;
|
||||
insertSpaceAfterKeywordsInControlFlowStatements?: boolean;
|
||||
insertSpaceAfterFunctionKeywordForAnonymousFunctions?: boolean;
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis?: boolean;
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets?: boolean;
|
||||
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces?: boolean;
|
||||
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean;
|
||||
insertSpaceBeforeFunctionParenthesis?: boolean;
|
||||
placeOpenBraceOnNewLineForFunctions?: boolean;
|
||||
placeOpenBraceOnNewLineForControlBlocks?: boolean;
|
||||
}
|
||||
|
||||
export interface CompilerOptions {
|
||||
allowJs?: boolean;
|
||||
allowSyntheticDefaultImports?: boolean;
|
||||
allowUnreachableCode?: boolean;
|
||||
allowUnusedLabels?: boolean;
|
||||
baseUrl?: string;
|
||||
charset?: string;
|
||||
declaration?: boolean;
|
||||
declarationDir?: string;
|
||||
disableSizeLimit?: boolean;
|
||||
emitBOM?: boolean;
|
||||
emitDecoratorMetadata?: boolean;
|
||||
experimentalDecorators?: boolean;
|
||||
forceConsistentCasingInFileNames?: boolean;
|
||||
inlineSourceMap?: boolean;
|
||||
inlineSources?: boolean;
|
||||
isolatedModules?: boolean;
|
||||
jsx?: JsxEmit | ts.JsxEmit;
|
||||
lib?: string[];
|
||||
locale?: string;
|
||||
mapRoot?: string;
|
||||
maxNodeModuleJsDepth?: number;
|
||||
module?: ModuleKind | ts.ModuleKind;
|
||||
moduleResolution?: ModuleResolutionKind | ts.ModuleResolutionKind;
|
||||
newLine?: NewLineKind | ts.NewLineKind;
|
||||
noEmit?: boolean;
|
||||
noEmitHelpers?: boolean;
|
||||
noEmitOnError?: boolean;
|
||||
noErrorTruncation?: boolean;
|
||||
noFallthroughCasesInSwitch?: boolean;
|
||||
noImplicitAny?: boolean;
|
||||
noImplicitReturns?: boolean;
|
||||
noImplicitThis?: boolean;
|
||||
noUnusedLocals?: boolean;
|
||||
noUnusedParameters?: boolean;
|
||||
noImplicitUseStrict?: boolean;
|
||||
noLib?: boolean;
|
||||
noResolve?: boolean;
|
||||
out?: string;
|
||||
outDir?: string;
|
||||
outFile?: string;
|
||||
paths?: MapLike<string[]>;
|
||||
preserveConstEnums?: boolean;
|
||||
project?: string;
|
||||
reactNamespace?: string;
|
||||
removeComments?: boolean;
|
||||
rootDir?: string;
|
||||
rootDirs?: string[];
|
||||
skipLibCheck?: boolean;
|
||||
skipDefaultLibCheck?: boolean;
|
||||
sourceMap?: boolean;
|
||||
sourceRoot?: string;
|
||||
strictNullChecks?: boolean;
|
||||
suppressExcessPropertyErrors?: boolean;
|
||||
suppressImplicitAnyIndexErrors?: boolean;
|
||||
target?: ScriptTarget | ts.ScriptTarget;
|
||||
traceResolution?: boolean;
|
||||
types?: string[];
|
||||
/** Paths used to used to compute primary types search locations */
|
||||
typeRoots?: string[];
|
||||
[option: string]: CompilerOptionsValue | undefined;
|
||||
}
|
||||
|
||||
export namespace JsxEmit {
|
||||
export type None = "None";
|
||||
export type Preserve = "Preserve";
|
||||
export type React = "React";
|
||||
}
|
||||
|
||||
export type JsxEmit = JsxEmit.None | JsxEmit.Preserve | JsxEmit.React;
|
||||
|
||||
export namespace ModuleKind {
|
||||
export type None = "None";
|
||||
export type CommonJS = "CommonJS";
|
||||
export type AMD = "AMD";
|
||||
export type UMD = "UMD";
|
||||
export type System = "System";
|
||||
export type ES6 = "ES6";
|
||||
export type ES2015 = "ES2015";
|
||||
}
|
||||
|
||||
export type ModuleKind = ModuleKind.None | ModuleKind.CommonJS | ModuleKind.AMD | ModuleKind.UMD | ModuleKind.System | ModuleKind.ES6 | ModuleKind.ES2015;
|
||||
|
||||
export namespace ModuleResolutionKind {
|
||||
export type Classic = "Classic";
|
||||
export type Node = "Node";
|
||||
}
|
||||
|
||||
export type ModuleResolutionKind = ModuleResolutionKind.Classic | ModuleResolutionKind.Node;
|
||||
|
||||
export namespace NewLineKind {
|
||||
export type Crlf = "Crlf";
|
||||
export type Lf = "Lf";
|
||||
}
|
||||
|
||||
export type NewLineKind = NewLineKind.Crlf | NewLineKind.Lf;
|
||||
|
||||
export namespace ScriptTarget {
|
||||
export type ES3 = "ES3";
|
||||
export type ES5 = "ES5";
|
||||
export type ES6 = "ES6";
|
||||
export type ES2015 = "ES2015";
|
||||
}
|
||||
|
||||
export type ScriptTarget = ScriptTarget.ES3 | ScriptTarget.ES5 | ScriptTarget.ES6 | ScriptTarget.ES2015;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,161 @@
|
||||
|
||||
namespace ts.server {
|
||||
|
||||
/* @internal */
|
||||
export class TextStorage {
|
||||
private svc: ScriptVersionCache | undefined;
|
||||
private svcVersion = 0;
|
||||
|
||||
private text: string;
|
||||
private lineMap: number[];
|
||||
private textVersion = 0;
|
||||
|
||||
constructor(private readonly host: ServerHost, private readonly fileName: NormalizedPath) {
|
||||
}
|
||||
|
||||
public getVersion() {
|
||||
return this.svc
|
||||
? `SVC-${this.svcVersion}-${this.svc.getSnapshot().version}`
|
||||
: `Text-${this.textVersion}`;
|
||||
}
|
||||
|
||||
public hasScriptVersionCache() {
|
||||
return this.svc !== undefined;
|
||||
}
|
||||
|
||||
public useScriptVersionCache(newText?: string) {
|
||||
this.switchToScriptVersionCache(newText);
|
||||
}
|
||||
|
||||
public useText(newText?: string) {
|
||||
this.svc = undefined;
|
||||
this.setText(newText);
|
||||
}
|
||||
|
||||
public edit(start: number, end: number, newText: string) {
|
||||
this.switchToScriptVersionCache().edit(start, end - start, newText);
|
||||
}
|
||||
|
||||
public reload(text: string) {
|
||||
if (this.svc) {
|
||||
this.svc.reload(text);
|
||||
}
|
||||
else {
|
||||
this.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
public reloadFromFile(tempFileName?: string) {
|
||||
if (this.svc || (tempFileName !== this.fileName)) {
|
||||
this.reload(this.getFileText(tempFileName))
|
||||
}
|
||||
else {
|
||||
this.setText(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
public getSnapshot(): IScriptSnapshot {
|
||||
return this.svc
|
||||
? this.svc.getSnapshot()
|
||||
: ScriptSnapshot.fromString(this.getOrLoadText());
|
||||
}
|
||||
|
||||
public getLineInfo(line: number) {
|
||||
return this.switchToScriptVersionCache().getSnapshot().index.lineNumberToInfo(line);
|
||||
}
|
||||
/**
|
||||
* @param line 0 based index
|
||||
*/
|
||||
lineToTextSpan(line: number) {
|
||||
if (!this.svc) {
|
||||
const lineMap = this.getLineMap();
|
||||
const start = lineMap[line]; // -1 since line is 1-based
|
||||
const end = line + 1 < lineMap.length ? lineMap[line + 1] : this.text.length;
|
||||
return ts.createTextSpanFromBounds(start, end);
|
||||
}
|
||||
const index = this.svc.getSnapshot().index;
|
||||
const lineInfo = index.lineNumberToInfo(line + 1);
|
||||
let len: number;
|
||||
if (lineInfo.leaf) {
|
||||
len = lineInfo.leaf.text.length;
|
||||
}
|
||||
else {
|
||||
const nextLineInfo = index.lineNumberToInfo(line + 2);
|
||||
len = nextLineInfo.offset - lineInfo.offset;
|
||||
}
|
||||
return ts.createTextSpan(lineInfo.offset, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param line 1 based index
|
||||
* @param offset 1 based index
|
||||
*/
|
||||
lineOffsetToPosition(line: number, offset: number): number {
|
||||
if (!this.svc) {
|
||||
return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1);
|
||||
}
|
||||
const index = this.svc.getSnapshot().index;
|
||||
|
||||
const lineInfo = index.lineNumberToInfo(line);
|
||||
// TODO: assert this offset is actually on the line
|
||||
return (lineInfo.offset + offset - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param line 1-based index
|
||||
* @param offset 1-based index
|
||||
*/
|
||||
positionToLineOffset(position: number): ILineInfo {
|
||||
if (!this.svc) {
|
||||
const { line, character } = computeLineAndCharacterOfPosition(this.getLineMap(), position);
|
||||
return { line: line + 1, offset: character + 1 };
|
||||
}
|
||||
const index = this.svc.getSnapshot().index;
|
||||
const lineOffset = index.charOffsetToLineNumberAndPos(position);
|
||||
return { line: lineOffset.line, offset: lineOffset.offset + 1 };
|
||||
}
|
||||
|
||||
private getFileText(tempFileName?: string) {
|
||||
return this.host.readFile(tempFileName || this.fileName) || "";
|
||||
}
|
||||
|
||||
private ensureNoScriptVersionCache() {
|
||||
Debug.assert(!this.svc, "ScriptVersionCache should not be set");
|
||||
}
|
||||
|
||||
private switchToScriptVersionCache(newText?: string): ScriptVersionCache {
|
||||
if (!this.svc) {
|
||||
this.svc = ScriptVersionCache.fromString(this.host, newText !== undefined ? newText : this.getOrLoadText());
|
||||
this.svcVersion++;
|
||||
this.text = undefined;
|
||||
}
|
||||
return this.svc;
|
||||
}
|
||||
|
||||
private getOrLoadText() {
|
||||
this.ensureNoScriptVersionCache();
|
||||
if (this.text === undefined) {
|
||||
this.setText(this.getFileText());
|
||||
}
|
||||
return this.text;
|
||||
}
|
||||
|
||||
private getLineMap() {
|
||||
this.ensureNoScriptVersionCache();
|
||||
return this.lineMap || (this.lineMap = computeLineStarts(this.getOrLoadText()));
|
||||
}
|
||||
|
||||
private setText(newText: string) {
|
||||
this.ensureNoScriptVersionCache();
|
||||
if (newText === undefined || this.text !== newText) {
|
||||
this.text = newText;
|
||||
this.lineMap = undefined;
|
||||
this.textVersion++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ScriptInfo {
|
||||
/**
|
||||
* All projects that include this file
|
||||
@@ -11,24 +166,46 @@ namespace ts.server {
|
||||
readonly path: Path;
|
||||
|
||||
private fileWatcher: FileWatcher;
|
||||
private svc: ScriptVersionCache;
|
||||
private textStorage: TextStorage;
|
||||
|
||||
private isOpen: boolean;
|
||||
|
||||
// TODO: allow to update hasMixedContent from the outside
|
||||
constructor(
|
||||
private readonly host: ServerHost,
|
||||
readonly fileName: NormalizedPath,
|
||||
content: string,
|
||||
readonly scriptKind: ScriptKind,
|
||||
public isOpen = false,
|
||||
public hasMixedContent = false) {
|
||||
|
||||
this.path = toPath(fileName, host.getCurrentDirectory(), createGetCanonicalFileName(host.useCaseSensitiveFileNames));
|
||||
this.svc = ScriptVersionCache.fromString(host, content);
|
||||
this.textStorage = new TextStorage(host, fileName);
|
||||
if (hasMixedContent) {
|
||||
this.textStorage.reload("");
|
||||
}
|
||||
this.scriptKind = scriptKind
|
||||
? scriptKind
|
||||
: getScriptKindFromFileName(fileName);
|
||||
}
|
||||
|
||||
public isScriptOpen() {
|
||||
return this.isOpen;
|
||||
}
|
||||
|
||||
public open(newText: string) {
|
||||
this.isOpen = true;
|
||||
this.textStorage.useScriptVersionCache(newText);
|
||||
this.markContainingProjectsAsDirty();
|
||||
}
|
||||
|
||||
public close() {
|
||||
this.isOpen = false;
|
||||
this.textStorage.useText(this.hasMixedContent ? "" : undefined);
|
||||
this.markContainingProjectsAsDirty();
|
||||
}
|
||||
|
||||
public getSnapshot() {
|
||||
return this.textStorage.getSnapshot();
|
||||
}
|
||||
|
||||
getFormatCodeSettings() {
|
||||
return this.formatCodeSettings;
|
||||
}
|
||||
@@ -87,10 +264,15 @@ namespace ts.server {
|
||||
if (this.containingProjects.length === 0) {
|
||||
return Errors.ThrowNoProject();
|
||||
}
|
||||
Debug.assert(this.containingProjects.length !== 0);
|
||||
return this.containingProjects[0];
|
||||
}
|
||||
|
||||
registerFileUpdate(): void {
|
||||
for (const p of this.containingProjects) {
|
||||
p.registerFileUpdate(this.path);
|
||||
}
|
||||
}
|
||||
|
||||
setFormatOptions(formatSettings: FormatCodeSettings): void {
|
||||
if (formatSettings) {
|
||||
if (!this.formatCodeSettings) {
|
||||
@@ -113,40 +295,35 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getLatestVersion() {
|
||||
return this.svc.latestVersion().toString();
|
||||
return this.textStorage.getVersion();
|
||||
}
|
||||
|
||||
reload(script: string) {
|
||||
this.svc.reload(script);
|
||||
this.textStorage.reload(script);
|
||||
this.markContainingProjectsAsDirty();
|
||||
}
|
||||
|
||||
saveTo(fileName: string) {
|
||||
const snap = this.snap();
|
||||
const snap = this.textStorage.getSnapshot();
|
||||
this.host.writeFile(fileName, snap.getText(0, snap.getLength()));
|
||||
}
|
||||
|
||||
reloadFromFile() {
|
||||
reloadFromFile(tempFileName?: NormalizedPath) {
|
||||
if (this.hasMixedContent) {
|
||||
this.reload("");
|
||||
}
|
||||
else {
|
||||
this.svc.reloadFromFile(this.fileName);
|
||||
this.textStorage.reloadFromFile(tempFileName);
|
||||
this.markContainingProjectsAsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
snap() {
|
||||
return this.svc.getSnapshot();
|
||||
}
|
||||
|
||||
getLineInfo(line: number) {
|
||||
const snap = this.snap();
|
||||
return snap.index.lineNumberToInfo(line);
|
||||
return this.textStorage.getLineInfo(line);
|
||||
}
|
||||
|
||||
editContent(start: number, end: number, newText: string): void {
|
||||
this.svc.edit(start, end - start, newText);
|
||||
this.textStorage.edit(start, end, newText);
|
||||
this.markContainingProjectsAsDirty();
|
||||
}
|
||||
|
||||
@@ -160,17 +337,7 @@ namespace ts.server {
|
||||
* @param line 1 based index
|
||||
*/
|
||||
lineToTextSpan(line: number) {
|
||||
const index = this.snap().index;
|
||||
const lineInfo = index.lineNumberToInfo(line + 1);
|
||||
let len: number;
|
||||
if (lineInfo.leaf) {
|
||||
len = lineInfo.leaf.text.length;
|
||||
}
|
||||
else {
|
||||
const nextLineInfo = index.lineNumberToInfo(line + 2);
|
||||
len = nextLineInfo.offset - lineInfo.offset;
|
||||
}
|
||||
return ts.createTextSpan(lineInfo.offset, len);
|
||||
return this.textStorage.lineToTextSpan(line);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,11 +345,7 @@ namespace ts.server {
|
||||
* @param offset 1 based index
|
||||
*/
|
||||
lineOffsetToPosition(line: number, offset: number): number {
|
||||
const index = this.snap().index;
|
||||
|
||||
const lineInfo = index.lineNumberToInfo(line);
|
||||
// TODO: assert this offset is actually on the line
|
||||
return (lineInfo.offset + offset - 1);
|
||||
return this.textStorage.lineOffsetToPosition(line, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,9 +353,7 @@ namespace ts.server {
|
||||
* @param offset 1-based index
|
||||
*/
|
||||
positionToLineOffset(position: number): ILineInfo {
|
||||
const index = this.snap().index;
|
||||
const lineOffset = index.charOffsetToLineNumberAndPos(position);
|
||||
return { line: lineOffset.line, offset: lineOffset.offset + 1 };
|
||||
return this.textStorage.positionToLineOffset(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ namespace ts.server {
|
||||
class BaseLineIndexWalker implements ILineIndexWalker {
|
||||
goSubtree = true;
|
||||
done = false;
|
||||
leaf(rangeStart: number, rangeLength: number, ll: LineLeaf) {
|
||||
leaf(_rangeStart: number, _rangeLength: number, _ll: LineLeaf) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace ts.server {
|
||||
if (len > 1) {
|
||||
let insertedNodes = <LineCollection[]>new Array(len - 1);
|
||||
let startNode = <LineCollection>leafNode;
|
||||
for (let i = 1, len = lines.length; i < len; i++) {
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
insertedNodes[i - 1] = new LineLeaf(lines[i]);
|
||||
}
|
||||
let pathIndex = this.startPath.length - 2;
|
||||
@@ -150,7 +150,7 @@ namespace ts.server {
|
||||
return this.lineIndex;
|
||||
}
|
||||
|
||||
post(relativeStart: number, relativeLength: number, lineCollection: LineCollection, parent: LineCollection, nodeType: CharRangeSection): LineCollection {
|
||||
post(_relativeStart: number, _relativeLength: number, lineCollection: LineCollection): LineCollection {
|
||||
// have visited the path for start of range, now looking for end
|
||||
// if range is on single line, we will never make this state transition
|
||||
if (lineCollection === this.lineCollectionAtBranch) {
|
||||
@@ -161,7 +161,7 @@ namespace ts.server {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
pre(relativeStart: number, relativeLength: number, lineCollection: LineCollection, parent: LineCollection, nodeType: CharRangeSection) {
|
||||
pre(_relativeStart: number, _relativeLength: number, lineCollection: LineCollection, _parent: LineCollection, nodeType: CharRangeSection) {
|
||||
// currentNode corresponds to parent, but in the new tree
|
||||
const currentNode = this.stack[this.stack.length - 1];
|
||||
|
||||
@@ -341,8 +341,7 @@ namespace ts.server {
|
||||
let snap = this.versions[this.currentVersionToIndex()];
|
||||
if (this.changes.length > 0) {
|
||||
let snapIndex = snap.index;
|
||||
for (let i = 0, len = this.changes.length; i < len; i++) {
|
||||
const change = this.changes[i];
|
||||
for (const change of this.changes) {
|
||||
snapIndex = snapIndex.edit(change.pos, change.deleteLen, change.insertedText);
|
||||
}
|
||||
snap = new LineIndexSnapshot(this.currentVersion + 1, this);
|
||||
@@ -366,8 +365,7 @@ namespace ts.server {
|
||||
const textChangeRanges: ts.TextChangeRange[] = [];
|
||||
for (let i = oldVersion + 1; i <= newVersion; i++) {
|
||||
const snap = this.versions[this.versionToIndex(i)];
|
||||
for (let j = 0, len = snap.changesSincePreviousVersion.length; j < len; j++) {
|
||||
const textChange = snap.changesSincePreviousVersion[j];
|
||||
for (const textChange of snap.changesSincePreviousVersion) {
|
||||
textChangeRanges[textChangeRanges.length] = textChange.getTextChangeRange();
|
||||
}
|
||||
}
|
||||
@@ -398,7 +396,7 @@ namespace ts.server {
|
||||
index: LineIndex;
|
||||
changesSincePreviousVersion: TextChange[] = [];
|
||||
|
||||
constructor(public version: number, public cache: ScriptVersionCache) {
|
||||
constructor(readonly version: number, readonly cache: ScriptVersionCache) {
|
||||
}
|
||||
|
||||
getText(rangeStart: number, rangeEnd: number) {
|
||||
@@ -414,7 +412,7 @@ namespace ts.server {
|
||||
const starts: number[] = [-1];
|
||||
let count = 1;
|
||||
let pos = 0;
|
||||
this.index.every((ll, s, len) => {
|
||||
this.index.every(ll => {
|
||||
starts[count] = pos;
|
||||
count++;
|
||||
pos += ll.text.length;
|
||||
@@ -438,8 +436,9 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
getChangeRange(oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange {
|
||||
const oldSnap = <LineIndexSnapshot>oldSnapshot;
|
||||
return this.getTextChangeRangeSinceVersion(oldSnap.version);
|
||||
if (oldSnapshot instanceof LineIndexSnapshot && this.cache === oldSnapshot.cache) {
|
||||
return this.getTextChangeRangeSinceVersion(oldSnapshot.version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,7 +469,7 @@ namespace ts.server {
|
||||
load(lines: string[]) {
|
||||
if (lines.length > 0) {
|
||||
const leaves: LineLeaf[] = [];
|
||||
for (let i = 0, len = lines.length; i < len; i++) {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
leaves[i] = new LineLeaf(lines[i]);
|
||||
}
|
||||
this.root = LineIndex.buildTreeFromBottom(leaves);
|
||||
@@ -642,8 +641,7 @@ namespace ts.server {
|
||||
updateCounts() {
|
||||
this.totalChars = 0;
|
||||
this.totalLines = 0;
|
||||
for (let i = 0, len = this.children.length; i < len; i++) {
|
||||
const child = this.children[i];
|
||||
for (const child of this.children) {
|
||||
this.totalChars += child.charCount();
|
||||
this.totalLines += child.lineCount();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/// <reference types="node" />
|
||||
/// <reference path="shared.ts" />
|
||||
/// <reference path="session.ts" />
|
||||
// used in fs.writeSync
|
||||
/* tslint:disable:no-null-keyword */
|
||||
@@ -14,32 +15,53 @@ namespace ts.server {
|
||||
} = require("child_process");
|
||||
|
||||
const os: {
|
||||
homedir(): string
|
||||
homedir?(): string;
|
||||
tmpdir(): string;
|
||||
} = require("os");
|
||||
|
||||
|
||||
function getGlobalTypingsCacheLocation() {
|
||||
let basePath: string;
|
||||
switch (process.platform) {
|
||||
case "win32":
|
||||
basePath = process.env.LOCALAPPDATA || process.env.APPDATA || os.homedir();
|
||||
break;
|
||||
case "linux":
|
||||
basePath = os.homedir();
|
||||
break;
|
||||
case "win32": {
|
||||
const basePath = process.env.LOCALAPPDATA ||
|
||||
process.env.APPDATA ||
|
||||
(os.homedir && os.homedir()) ||
|
||||
process.env.USERPROFILE ||
|
||||
(process.env.HOMEDRIVE && process.env.HOMEPATH && normalizeSlashes(process.env.HOMEDRIVE + process.env.HOMEPATH)) ||
|
||||
os.tmpdir();
|
||||
return combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript");
|
||||
}
|
||||
case "darwin":
|
||||
basePath = combinePaths(os.homedir(), "Library/Application Support/");
|
||||
break;
|
||||
case "linux":
|
||||
case "android": {
|
||||
const cacheLocation = getNonWindowsCacheLocation(process.platform === "darwin");
|
||||
return combinePaths(cacheLocation, "typescript");
|
||||
}
|
||||
default:
|
||||
Debug.fail(`unsupported platform '${process.platform}'`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.assert(basePath !== undefined);
|
||||
return combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript");
|
||||
function getNonWindowsCacheLocation(platformIsDarwin: boolean) {
|
||||
if (process.env.XDG_CACHE_HOME) {
|
||||
return process.env.XDG_CACHE_HOME;
|
||||
}
|
||||
const usersDir = platformIsDarwin ? "Users" : "home"
|
||||
const homePath = (os.homedir && os.homedir()) ||
|
||||
process.env.HOME ||
|
||||
((process.env.LOGNAME || process.env.USER) && `/${usersDir}/${process.env.LOGNAME || process.env.USER}`) ||
|
||||
os.tmpdir();
|
||||
const cacheFolder = platformIsDarwin
|
||||
? "Library/Caches"
|
||||
: ".cache"
|
||||
return combinePaths(normalizeSlashes(homePath), cacheFolder);
|
||||
}
|
||||
|
||||
interface NodeChildProcess {
|
||||
send(message: any, sendHandle?: any): void;
|
||||
on(message: "message", f: (m: any) => void): void;
|
||||
kill(): void;
|
||||
pid: number;
|
||||
}
|
||||
|
||||
interface NodeSocket {
|
||||
@@ -53,14 +75,6 @@ namespace ts.server {
|
||||
historySize?: number;
|
||||
}
|
||||
|
||||
interface Key {
|
||||
sequence?: string;
|
||||
name?: string;
|
||||
ctrl?: boolean;
|
||||
meta?: boolean;
|
||||
shift?: boolean;
|
||||
}
|
||||
|
||||
interface Stats {
|
||||
isFile(): boolean;
|
||||
isDirectory(): boolean;
|
||||
@@ -187,30 +201,58 @@ namespace ts.server {
|
||||
|
||||
class NodeTypingsInstaller implements ITypingsInstaller {
|
||||
private installer: NodeChildProcess;
|
||||
private installerPidReported = false;
|
||||
private socket: NodeSocket;
|
||||
private projectService: ProjectService;
|
||||
private throttledOperations: ThrottledOperations;
|
||||
private eventSender: EventSender;
|
||||
|
||||
constructor(
|
||||
private readonly telemetryEnabled: boolean,
|
||||
private readonly logger: server.Logger,
|
||||
private readonly eventPort: number,
|
||||
host: ServerHost,
|
||||
eventPort: number,
|
||||
readonly globalTypingsCacheLocation: string,
|
||||
private newLine: string) {
|
||||
this.throttledOperations = new ThrottledOperations(host);
|
||||
if (eventPort) {
|
||||
const s = net.connect({ port: eventPort }, () => {
|
||||
this.socket = s;
|
||||
this.reportInstallerProcessId();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private reportInstallerProcessId() {
|
||||
if (this.installerPidReported) {
|
||||
return;
|
||||
}
|
||||
if (this.socket && this.installer) {
|
||||
this.sendEvent(0, "typingsInstallerPid", { pid: this.installer.pid });
|
||||
this.installerPidReported = true;
|
||||
}
|
||||
}
|
||||
|
||||
private sendEvent(seq: number, event: string, body: any): void {
|
||||
this.socket.write(formatMessage({ seq, type: "event", event, body }, this.logger, Buffer.byteLength, this.newLine), "utf8");
|
||||
}
|
||||
|
||||
setTelemetrySender(telemetrySender: EventSender) {
|
||||
this.eventSender = telemetrySender;
|
||||
}
|
||||
|
||||
attach(projectService: ProjectService) {
|
||||
this.projectService = projectService;
|
||||
if (this.logger.hasLevel(LogLevel.requestTime)) {
|
||||
this.logger.info("Binding...");
|
||||
}
|
||||
|
||||
const args: string[] = ["--globalTypingsCacheLocation", this.globalTypingsCacheLocation];
|
||||
const args: string[] = [Arguments.GlobalCacheLocation, this.globalTypingsCacheLocation];
|
||||
if (this.telemetryEnabled) {
|
||||
args.push(Arguments.EnableTelemetry);
|
||||
}
|
||||
if (this.logger.loggingEnabled() && this.logger.getLogFileName()) {
|
||||
args.push("--logFile", combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`));
|
||||
args.push(Arguments.LogFile, combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`));
|
||||
}
|
||||
const execArgv: string[] = [];
|
||||
{
|
||||
@@ -218,7 +260,7 @@ namespace ts.server {
|
||||
const match = /^--(debug|inspect)(=(\d+))?$/.exec(arg);
|
||||
if (match) {
|
||||
// if port is specified - use port + 1
|
||||
// otherwise pick a default port depending on if 'debug' or 'inspect' and use its value + 1
|
||||
// otherwise pick a default port depending on if 'debug' or 'inspect' and use its value + 1
|
||||
const currentPort = match[3] !== undefined
|
||||
? +match[3]
|
||||
: match[1] === "debug" ? 5858 : 9229;
|
||||
@@ -230,6 +272,8 @@ namespace ts.server {
|
||||
|
||||
this.installer = childProcess.fork(combinePaths(__dirname, "typingsInstaller.js"), args, { execArgv });
|
||||
this.installer.on("message", m => this.handleMessage(m));
|
||||
this.reportInstallerProcessId();
|
||||
|
||||
process.on("exit", () => {
|
||||
this.installer.kill();
|
||||
});
|
||||
@@ -239,21 +283,70 @@ namespace ts.server {
|
||||
this.installer.send({ projectName: p.getProjectName(), kind: "closeProject" });
|
||||
}
|
||||
|
||||
enqueueInstallTypingsRequest(project: Project, typingOptions: TypingOptions): void {
|
||||
const request = createInstallTypingsRequest(project, typingOptions);
|
||||
enqueueInstallTypingsRequest(project: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>): void {
|
||||
const request = createInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Sending request: ${JSON.stringify(request)}`);
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Scheduling throttled operation: ${JSON.stringify(request)}`);
|
||||
}
|
||||
}
|
||||
this.installer.send(request);
|
||||
this.throttledOperations.schedule(project.getProjectName(), /*ms*/ 250, () => {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Sending request: ${JSON.stringify(request)}`);
|
||||
}
|
||||
this.installer.send(request);
|
||||
});
|
||||
}
|
||||
|
||||
private handleMessage(response: SetTypings | InvalidateCachedTypings) {
|
||||
private handleMessage(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes) {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Received response: ${JSON.stringify(response)}`);
|
||||
}
|
||||
|
||||
if (response.kind === EventBeginInstallTypes) {
|
||||
if (!this.eventSender) {
|
||||
return;
|
||||
}
|
||||
const body: protocol.BeginInstallTypesEventBody = {
|
||||
eventId: response.eventId,
|
||||
packages: response.packagesToInstall,
|
||||
};
|
||||
const eventName: protocol.BeginInstallTypesEventName = "beginInstallTypes";
|
||||
this.eventSender.event(body, eventName);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.kind === EventEndInstallTypes) {
|
||||
if (!this.eventSender) {
|
||||
return;
|
||||
}
|
||||
if (this.telemetryEnabled) {
|
||||
const body: protocol.TypingsInstalledTelemetryEventBody = {
|
||||
telemetryEventName: "typingsInstalled",
|
||||
payload: {
|
||||
installedPackages: response.packagesToInstall.join(","),
|
||||
installSuccess: response.installSuccess,
|
||||
typingsInstallerVersion: response.typingsInstallerVersion
|
||||
}
|
||||
};
|
||||
const eventName: protocol.TelemetryEventName = "telemetry";
|
||||
this.eventSender.event(body, eventName);
|
||||
}
|
||||
|
||||
const body: protocol.EndInstallTypesEventBody = {
|
||||
eventId: response.eventId,
|
||||
packages: response.packagesToInstall,
|
||||
success: response.installSuccess,
|
||||
};
|
||||
const eventName: protocol.EndInstallTypesEventName = "endInstallTypes";
|
||||
this.eventSender.event(body, eventName);
|
||||
return;
|
||||
}
|
||||
|
||||
this.projectService.updateTypingsForProject(response);
|
||||
if (response.kind == "set" && this.socket) {
|
||||
this.socket.write(formatMessage({ seq: 0, type: "event", message: response }, this.logger, Buffer.byteLength, this.newLine), "utf8");
|
||||
if (response.kind == ActionSet && this.socket) {
|
||||
this.sendEvent(0, "setTypings", response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,17 +358,27 @@ namespace ts.server {
|
||||
installerEventPort: number,
|
||||
canUseEvents: boolean,
|
||||
useSingleInferredProject: boolean,
|
||||
disableAutomaticTypingAcquisition: boolean,
|
||||
globalTypingsCacheLocation: string,
|
||||
telemetryEnabled: boolean,
|
||||
logger: server.Logger) {
|
||||
super(
|
||||
host,
|
||||
cancellationToken,
|
||||
useSingleInferredProject,
|
||||
new NodeTypingsInstaller(logger, installerEventPort, globalTypingsCacheLocation, host.newLine),
|
||||
Buffer.byteLength,
|
||||
process.hrtime,
|
||||
logger,
|
||||
canUseEvents);
|
||||
const typingsInstaller = disableAutomaticTypingAcquisition
|
||||
? undefined
|
||||
: new NodeTypingsInstaller(telemetryEnabled, logger, host, installerEventPort, globalTypingsCacheLocation, host.newLine);
|
||||
|
||||
super(
|
||||
host,
|
||||
cancellationToken,
|
||||
useSingleInferredProject,
|
||||
typingsInstaller || nullTypingsInstaller,
|
||||
Buffer.byteLength,
|
||||
process.hrtime,
|
||||
logger,
|
||||
canUseEvents);
|
||||
|
||||
if (telemetryEnabled && typingsInstaller) {
|
||||
typingsInstaller.setTelemetrySender(this);
|
||||
}
|
||||
}
|
||||
|
||||
exit() {
|
||||
@@ -306,7 +409,8 @@ namespace ts.server {
|
||||
function parseLoggingEnvironmentString(logEnvStr: string): LogOptions {
|
||||
const logEnv: LogOptions = { logToFile: true };
|
||||
const args = logEnvStr.split(" ");
|
||||
for (let i = 0, len = args.length; i < (len - 1); i += 2) {
|
||||
const len = args.length - 1;
|
||||
for (let i = 0; i < len; i += 2) {
|
||||
const option = args[i];
|
||||
const value = args[i + 1];
|
||||
if (option && value) {
|
||||
@@ -502,23 +606,31 @@ namespace ts.server {
|
||||
|
||||
let eventPort: number;
|
||||
{
|
||||
const index = sys.args.indexOf("--eventPort");
|
||||
if (index >= 0 && index < sys.args.length - 1) {
|
||||
const v = parseInt(sys.args[index + 1]);
|
||||
if (!isNaN(v)) {
|
||||
eventPort = v;
|
||||
}
|
||||
const str = findArgument("--eventPort");
|
||||
const v = str && parseInt(str);
|
||||
if (!isNaN(v)) {
|
||||
eventPort = v;
|
||||
}
|
||||
}
|
||||
|
||||
const useSingleInferredProject = sys.args.indexOf("--useSingleInferredProject") >= 0;
|
||||
const localeStr = findArgument("--locale");
|
||||
if (localeStr) {
|
||||
validateLocaleAndSetLanguage(localeStr, sys);
|
||||
}
|
||||
|
||||
const useSingleInferredProject = hasArgument("--useSingleInferredProject");
|
||||
const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition");
|
||||
const telemetryEnabled = hasArgument(Arguments.EnableTelemetry);
|
||||
|
||||
const ioSession = new IOSession(
|
||||
sys,
|
||||
cancellationToken,
|
||||
eventPort,
|
||||
/*canUseEvents*/ eventPort === undefined,
|
||||
useSingleInferredProject,
|
||||
disableAutomaticTypingAcquisition,
|
||||
getGlobalTypingsCacheLocation(),
|
||||
telemetryEnabled,
|
||||
logger);
|
||||
process.on("uncaughtException", function (err: Error) {
|
||||
ioSession.logError(err, "unknown");
|
||||
@@ -527,4 +639,4 @@ namespace ts.server {
|
||||
(process as any).noAsar = true;
|
||||
// Start listening
|
||||
ioSession.listen();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,10 @@ namespace ts.server {
|
||||
project: Project;
|
||||
}
|
||||
|
||||
export interface EventSender {
|
||||
event(payload: any, eventName: string): void;
|
||||
}
|
||||
|
||||
function allEditsBeforePos(edits: ts.TextChange[], pos: number) {
|
||||
for (const edit of edits) {
|
||||
if (textSpanEnd(edit.span) >= pos) {
|
||||
@@ -165,7 +169,7 @@ namespace ts.server {
|
||||
return `Content-Length: ${1 + len}\r\n\r\n${json}${newLine}`;
|
||||
}
|
||||
|
||||
export class Session {
|
||||
export class Session implements EventSender {
|
||||
private readonly gcTimer: GcTimer;
|
||||
protected projectService: ProjectService;
|
||||
private errorTimer: any; /*NodeJS.Timer | number*/
|
||||
@@ -195,15 +199,23 @@ namespace ts.server {
|
||||
|
||||
private defaultEventHandler(event: ProjectServiceEvent) {
|
||||
switch (event.eventName) {
|
||||
case "context":
|
||||
case ContextEvent:
|
||||
const { project, fileName } = event.data;
|
||||
this.projectService.logger.info(`got context event, updating diagnostics for ${fileName}`);
|
||||
this.updateErrorCheck([{ fileName, project }], this.changeSeq,
|
||||
(n) => n === this.changeSeq, 100);
|
||||
break;
|
||||
case "configFileDiag":
|
||||
case ConfigFileDiagEvent:
|
||||
const { triggerFile, configFileName, diagnostics } = event.data;
|
||||
this.configFileDiagnosticEvent(triggerFile, configFileName, diagnostics);
|
||||
break;
|
||||
case ProjectLanguageServiceStateEvent:
|
||||
const eventName: protocol.ProjectLanguageServiceStateEventName = "projectLanguageServiceState";
|
||||
this.event(<protocol.ProjectLanguageServiceStateEventBody>{
|
||||
projectName: event.data.project.getProjectName(),
|
||||
languageServiceEnabled: event.data.languageServiceEnabled
|
||||
}, eventName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,9 +401,9 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
private getDiagnosticsWorker(args: protocol.FileRequestArgs, selector: (project: Project, file: string) => Diagnostic[], includeLinePosition: boolean) {
|
||||
private getDiagnosticsWorker(args: protocol.FileRequestArgs, isSemantic: boolean, selector: (project: Project, file: string) => Diagnostic[], includeLinePosition: boolean) {
|
||||
const { project, file } = this.getFileAndProject(args);
|
||||
if (shouldSkipSematicCheck(project)) {
|
||||
if (isSemantic && shouldSkipSematicCheck(project)) {
|
||||
return [];
|
||||
}
|
||||
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
|
||||
@@ -492,11 +504,11 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private getSyntacticDiagnosticsSync(args: protocol.SyntacticDiagnosticsSyncRequestArgs): protocol.Diagnostic[] | protocol.DiagnosticWithLinePosition[] {
|
||||
return this.getDiagnosticsWorker(args, (project, file) => project.getLanguageService().getSyntacticDiagnostics(file), args.includeLinePosition);
|
||||
return this.getDiagnosticsWorker(args, /*isSemantic*/ false, (project, file) => project.getLanguageService().getSyntacticDiagnostics(file), args.includeLinePosition);
|
||||
}
|
||||
|
||||
private getSemanticDiagnosticsSync(args: protocol.SemanticDiagnosticsSyncRequestArgs): protocol.Diagnostic[] | protocol.DiagnosticWithLinePosition[] {
|
||||
return this.getDiagnosticsWorker(args, (project, file) => project.getLanguageService().getSemanticDiagnostics(file), args.includeLinePosition);
|
||||
return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSemanticDiagnostics(file), args.includeLinePosition);
|
||||
}
|
||||
|
||||
private getDocumentHighlights(args: protocol.DocumentHighlightsRequestArgs, simplifiedResult: boolean): protocol.DocumentHighlightsItem[] | DocumentHighlights[] {
|
||||
@@ -697,7 +709,7 @@ namespace ts.server {
|
||||
const displayString = ts.displayPartsToString(nameInfo.displayParts);
|
||||
const nameSpan = nameInfo.textSpan;
|
||||
const nameColStart = scriptInfo.positionToLineOffset(nameSpan.start).offset;
|
||||
const nameText = scriptInfo.snap().getText(nameSpan.start, ts.textSpanEnd(nameSpan));
|
||||
const nameText = scriptInfo.getSnapshot().getText(nameSpan.start, ts.textSpanEnd(nameSpan));
|
||||
const refs = combineProjectOutput<protocol.ReferencesResponseItem>(
|
||||
projects,
|
||||
(project: Project) => {
|
||||
@@ -710,7 +722,7 @@ namespace ts.server {
|
||||
const refScriptInfo = project.getScriptInfo(ref.fileName);
|
||||
const start = refScriptInfo.positionToLineOffset(ref.textSpan.start);
|
||||
const refLineSpan = refScriptInfo.lineToTextSpan(start.line - 1);
|
||||
const lineText = refScriptInfo.snap().getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, "");
|
||||
const lineText = refScriptInfo.getSnapshot().getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, "");
|
||||
return {
|
||||
file: ref.fileName,
|
||||
start: start,
|
||||
@@ -761,7 +773,7 @@ namespace ts.server {
|
||||
if (this.eventHander) {
|
||||
this.eventHander({
|
||||
eventName: "configFileDiag",
|
||||
data: { fileName, configFileName, diagnostics: configFileErrors || [] }
|
||||
data: { triggerFile: fileName, configFileName, diagnostics: configFileErrors || [] }
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -807,7 +819,7 @@ namespace ts.server {
|
||||
private getIndentation(args: protocol.IndentationRequestArgs) {
|
||||
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
|
||||
const position = this.getPosition(args, project.getScriptInfoForNormalizedPath(file));
|
||||
const options = args.options || this.projectService.getFormatCodeOptions(file);
|
||||
const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file);
|
||||
const indentation = project.getLanguageService(/*ensureSynchronized*/ false).getIndentationAtPosition(file, position, options);
|
||||
return { position, indentation };
|
||||
}
|
||||
@@ -874,19 +886,19 @@ namespace ts.server {
|
||||
|
||||
private getFormattingEditsForRangeFull(args: protocol.FormatRequestArgs) {
|
||||
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
|
||||
const options = args.options || this.projectService.getFormatCodeOptions(file);
|
||||
const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file);
|
||||
return project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsForRange(file, args.position, args.endPosition, options);
|
||||
}
|
||||
|
||||
private getFormattingEditsForDocumentFull(args: protocol.FormatRequestArgs) {
|
||||
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
|
||||
const options = args.options || this.projectService.getFormatCodeOptions(file);
|
||||
const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file);
|
||||
return project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsForDocument(file, options);
|
||||
}
|
||||
|
||||
private getFormattingEditsAfterKeystrokeFull(args: protocol.FormatOnKeyRequestArgs) {
|
||||
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
|
||||
const options = args.options || this.projectService.getFormatCodeOptions(file);
|
||||
const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file);
|
||||
return project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsAfterKeystroke(file, args.position, args.key, options);
|
||||
}
|
||||
|
||||
@@ -967,7 +979,7 @@ namespace ts.server {
|
||||
result.push({ name, kind, kindModifiers, sortText, replacementSpan: convertedSpan });
|
||||
}
|
||||
return result;
|
||||
}, []).sort((a, b) => a.name.localeCompare(b.name));
|
||||
}, []).sort((a, b) => ts.compareStrings(a.name, b.name));
|
||||
}
|
||||
else {
|
||||
return completions;
|
||||
@@ -1014,6 +1026,9 @@ namespace ts.server {
|
||||
if (!project) {
|
||||
Errors.ThrowNoProject();
|
||||
}
|
||||
if (!project.languageServiceEnabled) {
|
||||
return false;
|
||||
}
|
||||
const scriptInfo = project.getScriptInfo(file);
|
||||
return project.builder.emitFile(scriptInfo, (path, data, writeByteOrderMark) => this.host.writeFile(path, data, writeByteOrderMark));
|
||||
}
|
||||
@@ -1076,11 +1091,12 @@ namespace ts.server {
|
||||
|
||||
private reload(args: protocol.ReloadRequestArgs, reqSeq: number) {
|
||||
const file = toNormalizedPath(args.file);
|
||||
const tempFileName = args.tmpfile && toNormalizedPath(args.tmpfile);
|
||||
const project = this.projectService.getDefaultProjectForFile(file, /*refreshInferredProjects*/ true);
|
||||
if (project) {
|
||||
this.changeSeq++;
|
||||
// make sure no changes happen before this one is finished
|
||||
if (project.reloadScript(file)) {
|
||||
if (project.reloadScript(file, tempFileName)) {
|
||||
this.output(undefined, CommandNames.Reload, reqSeq);
|
||||
}
|
||||
}
|
||||
@@ -1299,7 +1315,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
// No need to analyze lib.d.ts
|
||||
let fileNamesInProject = fileNames.filter((value, index, array) => value.indexOf("lib.d.ts") < 0);
|
||||
let fileNamesInProject = fileNames.filter(value => value.indexOf("lib.d.ts") < 0);
|
||||
|
||||
// Sort the file name list to make the recently touched files come first
|
||||
const highPriorityFiles: NormalizedPath[] = [];
|
||||
@@ -1313,7 +1329,7 @@ namespace ts.server {
|
||||
highPriorityFiles.push(fileNameInProject);
|
||||
else {
|
||||
const info = this.projectService.getScriptInfo(fileNameInProject);
|
||||
if (!info.isOpen) {
|
||||
if (!info.isScriptOpen()) {
|
||||
if (fileNameInProject.indexOf(".d.ts") > 0)
|
||||
veryLowPriorityFiles.push(fileNameInProject);
|
||||
else
|
||||
@@ -1352,14 +1368,12 @@ namespace ts.server {
|
||||
|
||||
private handlers = createMap<(request: protocol.Request) => { response?: any, responseRequired?: boolean }>({
|
||||
[CommandNames.OpenExternalProject]: (request: protocol.OpenExternalProjectRequest) => {
|
||||
this.projectService.openExternalProject(request.arguments);
|
||||
this.projectService.openExternalProject(request.arguments, /*suppressRefreshOfInferredProjects*/ false);
|
||||
// TODO: report errors
|
||||
return this.requiredResponse(true);
|
||||
},
|
||||
[CommandNames.OpenExternalProjects]: (request: protocol.OpenExternalProjectsRequest) => {
|
||||
for (const proj of request.arguments.projects) {
|
||||
this.projectService.openExternalProject(proj);
|
||||
}
|
||||
this.projectService.openExternalProjects(request.arguments.projects);
|
||||
// TODO: report errors
|
||||
return this.requiredResponse(true);
|
||||
},
|
||||
@@ -1426,24 +1440,8 @@ namespace ts.server {
|
||||
[CommandNames.RenameInfoFull]: (request: protocol.FileLocationRequest) => {
|
||||
return this.requiredResponse(this.getRenameInfo(request.arguments));
|
||||
},
|
||||
[CommandNames.Open]: (request: protocol.Request) => {
|
||||
const openArgs = <protocol.OpenRequestArgs>request.arguments;
|
||||
let scriptKind: ScriptKind;
|
||||
switch (openArgs.scriptKindName) {
|
||||
case "TS":
|
||||
scriptKind = ScriptKind.TS;
|
||||
break;
|
||||
case "JS":
|
||||
scriptKind = ScriptKind.JS;
|
||||
break;
|
||||
case "TSX":
|
||||
scriptKind = ScriptKind.TSX;
|
||||
break;
|
||||
case "JSX":
|
||||
scriptKind = ScriptKind.JSX;
|
||||
break;
|
||||
}
|
||||
this.openClientFile(toNormalizedPath(openArgs.file), openArgs.fileContent, scriptKind);
|
||||
[CommandNames.Open]: (request: protocol.OpenRequest) => {
|
||||
this.openClientFile(toNormalizedPath(request.arguments.file), request.arguments.fileContent, convertScriptKindName(request.arguments.scriptKindName));
|
||||
return this.notRequired();
|
||||
},
|
||||
[CommandNames.Quickinfo]: (request: protocol.QuickInfoRequest) => {
|
||||
@@ -1515,7 +1513,7 @@ namespace ts.server {
|
||||
[CommandNames.EncodedSemanticClassificationsFull]: (request: protocol.EncodedSemanticClassificationsRequest) => {
|
||||
return this.requiredResponse(this.getEncodedSemanticClassifications(request.arguments));
|
||||
},
|
||||
[CommandNames.Cleanup]: (request: protocol.Request) => {
|
||||
[CommandNames.Cleanup]: () => {
|
||||
this.cleanup();
|
||||
return this.requiredResponse(true);
|
||||
},
|
||||
@@ -1590,12 +1588,13 @@ namespace ts.server {
|
||||
return this.requiredResponse(this.getDocumentHighlights(request.arguments, /*simplifiedResult*/ false));
|
||||
},
|
||||
[CommandNames.CompilerOptionsForInferredProjects]: (request: protocol.SetCompilerOptionsForInferredProjectsRequest) => {
|
||||
return this.requiredResponse(this.setCompilerOptionsForInferredProjects(request.arguments));
|
||||
this.setCompilerOptionsForInferredProjects(request.arguments);
|
||||
return this.requiredResponse(true);
|
||||
},
|
||||
[CommandNames.ProjectInfo]: (request: protocol.ProjectInfoRequest) => {
|
||||
return this.requiredResponse(this.getProjectInfo(request.arguments));
|
||||
},
|
||||
[CommandNames.ReloadProjects]: (request: protocol.ReloadProjectsRequest) => {
|
||||
[CommandNames.ReloadProjects]: () => {
|
||||
this.projectService.reloadProjects();
|
||||
return this.notRequired();
|
||||
},
|
||||
@@ -1605,7 +1604,7 @@ namespace ts.server {
|
||||
[CommandNames.GetCodeFixesFull]: (request: protocol.CodeFixRequest) => {
|
||||
return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ false));
|
||||
},
|
||||
[CommandNames.GetSupportedCodeFixes]: (request: protocol.Request) => {
|
||||
[CommandNames.GetSupportedCodeFixes]: () => {
|
||||
return this.requiredResponse(this.getSupportedCodeFixes());
|
||||
}
|
||||
});
|
||||
|
||||
25
src/server/shared.ts
Normal file
25
src/server/shared.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/// <reference path="types.d.ts" />
|
||||
|
||||
namespace ts.server {
|
||||
export const ActionSet: ActionSet = "action::set";
|
||||
export const ActionInvalidate: ActionInvalidate = "action::invalidate";
|
||||
export const EventBeginInstallTypes: EventBeginInstallTypes = "event::beginInstallTypes";
|
||||
export const EventEndInstallTypes: EventEndInstallTypes = "event::endInstallTypes";
|
||||
|
||||
export namespace Arguments {
|
||||
export const GlobalCacheLocation = "--globalTypingsCacheLocation";
|
||||
export const LogFile = "--logFile";
|
||||
export const EnableTelemetry = "--enableTelemetry";
|
||||
}
|
||||
|
||||
export function hasArgument(argumentName: string) {
|
||||
return sys.args.indexOf(argumentName) >= 0;
|
||||
}
|
||||
|
||||
export function findArgument(argumentName: string) {
|
||||
const index = sys.args.indexOf(argumentName);
|
||||
return index >= 0 && index < sys.args.length - 1
|
||||
? sys.args[index + 1]
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,15 @@
|
||||
"stripInternal": true,
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
],
|
||||
"target": "es5",
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
},
|
||||
"files": [
|
||||
"../services/shims.ts",
|
||||
"../services/utilities.ts",
|
||||
"shared.ts",
|
||||
"utilities.ts",
|
||||
"scriptVersionCache.ts",
|
||||
"scriptInfo.ts",
|
||||
|
||||
@@ -7,11 +7,15 @@
|
||||
"sourceMap": true,
|
||||
"stripInternal": true,
|
||||
"declaration": true,
|
||||
"types": []
|
||||
"types": [],
|
||||
"target": "es5",
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
},
|
||||
"files": [
|
||||
"../services/shims.ts",
|
||||
"../services/utilities.ts",
|
||||
"shared.ts",
|
||||
"utilities.ts",
|
||||
"scriptVersionCache.ts",
|
||||
"scriptInfo.ts",
|
||||
|
||||
48
src/server/types.d.ts
vendored
48
src/server/types.d.ts
vendored
@@ -18,6 +18,10 @@ declare namespace ts.server {
|
||||
trace?(s: string): void;
|
||||
}
|
||||
|
||||
export interface SortedReadonlyArray<T> extends ReadonlyArray<T> {
|
||||
" __sortedReadonlyArrayBrand": any;
|
||||
}
|
||||
|
||||
export interface TypingInstallerRequest {
|
||||
readonly projectName: string;
|
||||
readonly kind: "discover" | "closeProject";
|
||||
@@ -26,8 +30,9 @@ declare namespace ts.server {
|
||||
export interface DiscoverTypings extends TypingInstallerRequest {
|
||||
readonly fileNames: string[];
|
||||
readonly projectRootPath: ts.Path;
|
||||
readonly typingOptions: ts.TypingOptions;
|
||||
readonly compilerOptions: ts.CompilerOptions;
|
||||
readonly typeAcquisition: ts.TypeAcquisition;
|
||||
readonly unresolvedImports: SortedReadonlyArray<string>;
|
||||
readonly cachePath?: string;
|
||||
readonly kind: "discover";
|
||||
}
|
||||
@@ -36,25 +41,50 @@ declare namespace ts.server {
|
||||
readonly kind: "closeProject";
|
||||
}
|
||||
|
||||
export type ActionSet = "action::set";
|
||||
export type ActionInvalidate = "action::invalidate";
|
||||
export type EventBeginInstallTypes = "event::beginInstallTypes";
|
||||
export type EventEndInstallTypes = "event::endInstallTypes";
|
||||
|
||||
export interface TypingInstallerResponse {
|
||||
readonly projectName: string;
|
||||
readonly kind: "set" | "invalidate";
|
||||
readonly kind: ActionSet | ActionInvalidate | EventBeginInstallTypes | EventEndInstallTypes;
|
||||
}
|
||||
|
||||
export interface SetTypings extends TypingInstallerResponse {
|
||||
readonly typingOptions: ts.TypingOptions;
|
||||
export interface ProjectResponse extends TypingInstallerResponse {
|
||||
readonly projectName: string;
|
||||
}
|
||||
|
||||
export interface SetTypings extends ProjectResponse {
|
||||
readonly typeAcquisition: ts.TypeAcquisition;
|
||||
readonly compilerOptions: ts.CompilerOptions;
|
||||
readonly typings: string[];
|
||||
readonly kind: "set";
|
||||
readonly unresolvedImports: SortedReadonlyArray<string>;
|
||||
readonly kind: ActionSet;
|
||||
}
|
||||
|
||||
export interface InvalidateCachedTypings extends TypingInstallerResponse {
|
||||
readonly kind: "invalidate";
|
||||
export interface InvalidateCachedTypings extends ProjectResponse {
|
||||
readonly kind: ActionInvalidate;
|
||||
}
|
||||
|
||||
export interface InstallTypes extends ProjectResponse {
|
||||
readonly kind: EventBeginInstallTypes | EventEndInstallTypes;
|
||||
readonly eventId: number;
|
||||
readonly typingsInstallerVersion: string;
|
||||
readonly packagesToInstall: ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
export interface BeginInstallTypes extends InstallTypes {
|
||||
readonly kind: EventBeginInstallTypes;
|
||||
}
|
||||
|
||||
export interface EndInstallTypes extends InstallTypes {
|
||||
readonly kind: EventEndInstallTypes;
|
||||
readonly installSuccess: boolean;
|
||||
}
|
||||
|
||||
export interface InstallTypingHost extends JsTyping.TypingResolutionHost {
|
||||
writeFile(path: string, content: string): void;
|
||||
createDirectory(path: string): void;
|
||||
watchFile?(path: string, callback: FileWatcherCallback): FileWatcher;
|
||||
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
|
||||
}
|
||||
}
|
||||
@@ -2,23 +2,25 @@
|
||||
|
||||
namespace ts.server {
|
||||
export interface ITypingsInstaller {
|
||||
enqueueInstallTypingsRequest(p: Project, typingOptions: TypingOptions): void;
|
||||
enqueueInstallTypingsRequest(p: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>): void;
|
||||
attach(projectService: ProjectService): void;
|
||||
onProjectClosed(p: Project): void;
|
||||
readonly globalTypingsCacheLocation: string;
|
||||
}
|
||||
|
||||
export const nullTypingsInstaller: ITypingsInstaller = {
|
||||
enqueueInstallTypingsRequest: () => {},
|
||||
attach: (projectService: ProjectService) => {},
|
||||
onProjectClosed: (p: Project) => {},
|
||||
enqueueInstallTypingsRequest: noop,
|
||||
attach: noop,
|
||||
onProjectClosed: noop,
|
||||
globalTypingsCacheLocation: undefined
|
||||
};
|
||||
|
||||
class TypingsCacheEntry {
|
||||
readonly typingOptions: TypingOptions;
|
||||
readonly typeAcquisition: TypeAcquisition;
|
||||
readonly compilerOptions: CompilerOptions;
|
||||
readonly typings: TypingsArray;
|
||||
readonly typings: SortedReadonlyArray<string>;
|
||||
readonly unresolvedImports: SortedReadonlyArray<string>;
|
||||
/* mainly useful for debugging */
|
||||
poisoned: boolean;
|
||||
}
|
||||
|
||||
@@ -50,8 +52,8 @@ namespace ts.server {
|
||||
return unique === 0;
|
||||
}
|
||||
|
||||
function typingOptionsChanged(opt1: TypingOptions, opt2: TypingOptions): boolean {
|
||||
return opt1.enableAutoDiscovery !== opt2.enableAutoDiscovery ||
|
||||
function typeAcquisitionChanged(opt1: TypeAcquisition, opt2: TypeAcquisition): boolean {
|
||||
return opt1.enable !== opt2.enable ||
|
||||
!setIsEqualTo(opt1.include, opt2.include) ||
|
||||
!setIsEqualTo(opt1.exclude, opt2.exclude);
|
||||
}
|
||||
@@ -61,13 +63,11 @@ namespace ts.server {
|
||||
return opt1.allowJs != opt2.allowJs;
|
||||
}
|
||||
|
||||
export interface TypingsArray extends ReadonlyArray<string> {
|
||||
" __typingsArrayBrand": any;
|
||||
}
|
||||
|
||||
function toTypingsArray(arr: string[]): TypingsArray {
|
||||
arr.sort();
|
||||
return <any>arr;
|
||||
function unresolvedImportsChanged(imports1: SortedReadonlyArray<string>, imports2: SortedReadonlyArray<string>): boolean {
|
||||
if (imports1 === imports2) {
|
||||
return false;
|
||||
}
|
||||
return !arrayIsEqualTo(imports1, imports2);
|
||||
}
|
||||
|
||||
export class TypingsCache {
|
||||
@@ -76,47 +76,49 @@ namespace ts.server {
|
||||
constructor(private readonly installer: ITypingsInstaller) {
|
||||
}
|
||||
|
||||
getTypingsForProject(project: Project, forceRefresh: boolean): TypingsArray {
|
||||
const typingOptions = project.getTypingOptions();
|
||||
getTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray<string>, forceRefresh: boolean): SortedReadonlyArray<string> {
|
||||
const typeAcquisition = project.getTypeAcquisition();
|
||||
|
||||
if (!typingOptions || !typingOptions.enableAutoDiscovery) {
|
||||
if (!typeAcquisition || !typeAcquisition.enable) {
|
||||
return <any>emptyArray;
|
||||
}
|
||||
|
||||
const entry = this.perProjectCache[project.getProjectName()];
|
||||
const result: TypingsArray = entry ? entry.typings : <any>emptyArray;
|
||||
if (forceRefresh || !entry || typingOptionsChanged(typingOptions, entry.typingOptions) || compilerOptionsChanged(project.getCompilerOptions(), entry.compilerOptions)) {
|
||||
const result: SortedReadonlyArray<string> = entry ? entry.typings : <any>emptyArray;
|
||||
if (forceRefresh ||
|
||||
!entry ||
|
||||
typeAcquisitionChanged(typeAcquisition, entry.typeAcquisition) ||
|
||||
compilerOptionsChanged(project.getCompilerOptions(), entry.compilerOptions) ||
|
||||
unresolvedImportsChanged(unresolvedImports, entry.unresolvedImports)) {
|
||||
// Note: entry is now poisoned since it does not really contain typings for a given combination of compiler options\typings options.
|
||||
// instead it acts as a placeholder to prevent issuing multiple requests
|
||||
this.perProjectCache[project.getProjectName()] = {
|
||||
compilerOptions: project.getCompilerOptions(),
|
||||
typingOptions,
|
||||
typeAcquisition,
|
||||
typings: result,
|
||||
unresolvedImports,
|
||||
poisoned: true
|
||||
};
|
||||
// something has been changed, issue a request to update typings
|
||||
this.installer.enqueueInstallTypingsRequest(project, typingOptions);
|
||||
this.installer.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
invalidateCachedTypingsForProject(project: Project) {
|
||||
const typingOptions = project.getTypingOptions();
|
||||
if (!typingOptions.enableAutoDiscovery) {
|
||||
return;
|
||||
}
|
||||
this.installer.enqueueInstallTypingsRequest(project, typingOptions);
|
||||
}
|
||||
|
||||
updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typingOptions: TypingOptions, newTypings: string[]) {
|
||||
updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>, newTypings: string[]) {
|
||||
this.perProjectCache[projectName] = {
|
||||
compilerOptions,
|
||||
typingOptions,
|
||||
typings: toTypingsArray(newTypings),
|
||||
typeAcquisition,
|
||||
typings: toSortedReadonlyArray(newTypings),
|
||||
unresolvedImports,
|
||||
poisoned: false
|
||||
};
|
||||
}
|
||||
|
||||
deleteTypingsForProject(projectName: string) {
|
||||
delete this.perProjectCache[projectName];
|
||||
}
|
||||
|
||||
onProjectClosed(project: Project) {
|
||||
delete this.perProjectCache[project.getProjectName()];
|
||||
this.installer.onProjectClosed(project);
|
||||
|
||||
@@ -33,27 +33,74 @@ namespace ts.server.typingsInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeTypingsInstaller extends TypingsInstaller {
|
||||
private readonly exec: { (command: string, options: { cwd: string }, callback?: (error: Error, stdout: string, stderr: string) => void): any };
|
||||
interface TypesRegistryFile {
|
||||
entries: MapLike<void>;
|
||||
}
|
||||
|
||||
readonly installTypingHost: InstallTypingHost = sys;
|
||||
function loadTypesRegistryFile(typesRegistryFilePath: string, host: InstallTypingHost, log: Log): Map<void> {
|
||||
if (!host.fileExists(typesRegistryFilePath)) {
|
||||
if (log.isEnabled()) {
|
||||
log.writeLine(`Types registry file '${typesRegistryFilePath}' does not exist`);
|
||||
}
|
||||
return createMap<void>();
|
||||
}
|
||||
try {
|
||||
const content = <TypesRegistryFile>JSON.parse(host.readFile(typesRegistryFilePath));
|
||||
return createMap<void>(content.entries);
|
||||
}
|
||||
catch (e) {
|
||||
if (log.isEnabled()) {
|
||||
log.writeLine(`Error when loading types registry file '${typesRegistryFilePath}': ${(<Error>e).message}, ${(<Error>e).stack}`);
|
||||
}
|
||||
return createMap<void>();
|
||||
}
|
||||
}
|
||||
|
||||
const TypesRegistryPackageName = "types-registry";
|
||||
function getTypesRegistryFileLocation(globalTypingsCacheLocation: string): string {
|
||||
return combinePaths(normalizeSlashes(globalTypingsCacheLocation), `node_modules/${TypesRegistryPackageName}/index.json`);
|
||||
}
|
||||
|
||||
type ExecSync = {
|
||||
(command: string, options: { cwd: string, stdio?: "ignore" }): any
|
||||
}
|
||||
|
||||
export class NodeTypingsInstaller extends TypingsInstaller {
|
||||
private readonly execSync: ExecSync;
|
||||
private readonly npmPath: string;
|
||||
readonly typesRegistry: Map<void>;
|
||||
|
||||
constructor(globalTypingsCacheLocation: string, throttleLimit: number, log: Log) {
|
||||
super(
|
||||
sys,
|
||||
globalTypingsCacheLocation,
|
||||
/*npmPath*/ getNPMLocation(process.argv[0]),
|
||||
toPath("typingSafeList.json", __dirname, createGetCanonicalFileName(sys.useCaseSensitiveFileNames)),
|
||||
throttleLimit,
|
||||
log);
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Process id: ${process.pid}`);
|
||||
}
|
||||
const { exec } = require("child_process");
|
||||
this.exec = exec;
|
||||
this.npmPath = getNPMLocation(process.argv[0]);
|
||||
({ execSync: this.execSync } = require("child_process"));
|
||||
|
||||
this.ensurePackageDirectoryExists(globalTypingsCacheLocation);
|
||||
|
||||
try {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Updating ${TypesRegistryPackageName} npm package...`);
|
||||
}
|
||||
this.execSync(`${this.npmPath} install ${TypesRegistryPackageName}`, { cwd: globalTypingsCacheLocation, stdio: "ignore" });
|
||||
}
|
||||
catch (e) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Error updating ${TypesRegistryPackageName} package: ${(<Error>e).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
this.typesRegistry = loadTypesRegistryFile(getTypesRegistryFileLocation(globalTypingsCacheLocation), this.installTypingHost, this.log);
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init();
|
||||
listen() {
|
||||
process.on("message", (req: DiscoverTypings | CloseProject) => {
|
||||
switch (req.kind) {
|
||||
case "discover":
|
||||
@@ -65,7 +112,7 @@ namespace ts.server.typingsInstaller {
|
||||
});
|
||||
}
|
||||
|
||||
protected sendResponse(response: SetTypings | InvalidateCachedTypings) {
|
||||
protected sendResponse(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Sending response: ${JSON.stringify(response)}`);
|
||||
}
|
||||
@@ -75,29 +122,33 @@ namespace ts.server.typingsInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
protected runCommand(requestKind: RequestKind, requestId: number, command: string, cwd: string, onRequestCompleted: RequestCompletedAction): void {
|
||||
protected installWorker(requestId: number, args: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`#${requestId} running command '${command}'.`);
|
||||
this.log.writeLine(`#${requestId} with arguments'${JSON.stringify(args)}'.`);
|
||||
}
|
||||
this.exec(command, { cwd }, (err, stdout, stderr) => {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`${requestKind} #${requestId} stdout: ${stdout}`);
|
||||
this.log.writeLine(`${requestKind} #${requestId} stderr: ${stderr}`);
|
||||
}
|
||||
onRequestCompleted(err, stdout, stderr);
|
||||
});
|
||||
const command = `${this.npmPath} install ${args.join(" ")} --save-dev --user-agent="typesInstaller/${version}"`;
|
||||
const start = Date.now();
|
||||
let stdout: Buffer;
|
||||
let stderr: Buffer;
|
||||
let hasError = false;
|
||||
try {
|
||||
stdout = this.execSync(command, { cwd });
|
||||
}
|
||||
catch (e) {
|
||||
stdout = e.stdout;
|
||||
stderr = e.stderr;
|
||||
hasError = true;
|
||||
}
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`npm install #${requestId} took: ${Date.now() - start} ms${sys.newLine}stdout: ${stdout && stdout.toString()}${sys.newLine}stderr: ${stderr && stderr.toString()}`);
|
||||
}
|
||||
onRequestCompleted(!hasError);
|
||||
}
|
||||
}
|
||||
|
||||
function findArgument(argumentName: string) {
|
||||
const index = sys.args.indexOf(argumentName);
|
||||
return index >= 0 && index < sys.args.length - 1
|
||||
? sys.args[index + 1]
|
||||
: undefined;
|
||||
}
|
||||
const logFilePath = findArgument(server.Arguments.LogFile);
|
||||
const globalTypingsCacheLocation = findArgument(server.Arguments.GlobalCacheLocation);
|
||||
|
||||
const logFilePath = findArgument("--logFile");
|
||||
const globalTypingsCacheLocation = findArgument("--globalTypingsCacheLocation");
|
||||
const log = new FileLog(logFilePath);
|
||||
if (log.isEnabled()) {
|
||||
process.on("uncaughtException", (e: Error) => {
|
||||
@@ -111,5 +162,5 @@ namespace ts.server.typingsInstaller {
|
||||
process.exit(0);
|
||||
});
|
||||
const installer = new NodeTypingsInstaller(globalTypingsCacheLocation, /*throttleLimit*/5, log);
|
||||
installer.init();
|
||||
installer.listen();
|
||||
}
|
||||
@@ -10,10 +10,14 @@
|
||||
"stripInternal": true,
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
],
|
||||
"target": "es5",
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
},
|
||||
"files": [
|
||||
"../types.d.ts",
|
||||
"../shared.ts",
|
||||
"typingsInstaller.ts",
|
||||
"nodeTypingsInstaller.ts"
|
||||
]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/// <reference path="../../compiler/moduleNameResolver.ts" />
|
||||
/// <reference path="../../services/jsTyping.ts"/>
|
||||
/// <reference path="../types.d.ts"/>
|
||||
/// <reference path="../shared.ts"/>
|
||||
|
||||
namespace ts.server.typingsInstaller {
|
||||
interface NpmConfig {
|
||||
@@ -15,17 +16,26 @@ namespace ts.server.typingsInstaller {
|
||||
|
||||
const nullLog: Log = {
|
||||
isEnabled: () => false,
|
||||
writeLine: () => {}
|
||||
writeLine: noop
|
||||
};
|
||||
|
||||
function typingToFileName(cachePath: string, packageName: string, installTypingHost: InstallTypingHost): string {
|
||||
const result = resolveModuleName(packageName, combinePaths(cachePath, "index.d.ts"), { moduleResolution: ModuleResolutionKind.NodeJs }, installTypingHost);
|
||||
return result.resolvedModule && result.resolvedModule.resolvedFileName;
|
||||
function typingToFileName(cachePath: string, packageName: string, installTypingHost: InstallTypingHost, log: Log): string {
|
||||
try {
|
||||
const result = resolveModuleName(packageName, combinePaths(cachePath, "index.d.ts"), { moduleResolution: ModuleResolutionKind.NodeJs }, installTypingHost);
|
||||
return result.resolvedModule && result.resolvedModule.resolvedFileName;
|
||||
}
|
||||
catch (e) {
|
||||
if (log.isEnabled()) {
|
||||
log.writeLine(`Failed to resolve ${packageName} in folder '${cachePath}': ${(<Error>e).message}`);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export enum PackageNameValidationResult {
|
||||
Ok,
|
||||
ScopedPackagesNotSupported,
|
||||
EmptyName,
|
||||
NameTooLong,
|
||||
NameStartsWithDot,
|
||||
NameStartsWithUnderscore,
|
||||
@@ -35,10 +45,12 @@ namespace ts.server.typingsInstaller {
|
||||
|
||||
export const MaxPackageNameLength = 214;
|
||||
/**
|
||||
* Validates package name using rules defined at https://docs.npmjs.com/files/package.json
|
||||
* Validates package name using rules defined at https://docs.npmjs.com/files/package.json
|
||||
*/
|
||||
export function validatePackageName(packageName: string): PackageNameValidationResult {
|
||||
Debug.assert(!!packageName, "Package name is not specified");
|
||||
if (!packageName) {
|
||||
return PackageNameValidationResult.EmptyName;
|
||||
}
|
||||
if (packageName.length > MaxPackageNameLength) {
|
||||
return PackageNameValidationResult.NameTooLong;
|
||||
}
|
||||
@@ -60,18 +72,12 @@ namespace ts.server.typingsInstaller {
|
||||
return PackageNameValidationResult.Ok;
|
||||
}
|
||||
|
||||
export const NpmViewRequest: "npm view" = "npm view";
|
||||
export const NpmInstallRequest: "npm install" = "npm install";
|
||||
|
||||
export type RequestKind = typeof NpmViewRequest | typeof NpmInstallRequest;
|
||||
|
||||
export type RequestCompletedAction = (err: Error, stdout: string, stderr: string) => void;
|
||||
export type RequestCompletedAction = (success: boolean) => void;
|
||||
type PendingRequest = {
|
||||
requestKind: RequestKind;
|
||||
requestId: number;
|
||||
command: string;
|
||||
args: string[];
|
||||
cwd: string;
|
||||
onRequestCompleted: RequestCompletedAction
|
||||
onRequestCompleted: RequestCompletedAction;
|
||||
};
|
||||
|
||||
export abstract class TypingsInstaller {
|
||||
@@ -84,20 +90,17 @@ namespace ts.server.typingsInstaller {
|
||||
private installRunCount = 1;
|
||||
private inFlightRequestCount = 0;
|
||||
|
||||
abstract readonly installTypingHost: InstallTypingHost;
|
||||
abstract readonly typesRegistry: Map<void>;
|
||||
|
||||
constructor(
|
||||
readonly installTypingHost: InstallTypingHost,
|
||||
readonly globalCachePath: string,
|
||||
readonly npmPath: string,
|
||||
readonly safeListPath: Path,
|
||||
readonly throttleLimit: number,
|
||||
protected readonly log = nullLog) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Global cache location '${globalCachePath}', safe file path '${safeListPath}'`);
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this.processCacheLocation(this.globalCachePath);
|
||||
}
|
||||
|
||||
@@ -132,7 +135,7 @@ namespace ts.server.typingsInstaller {
|
||||
this.log.writeLine(`Got install request ${JSON.stringify(req)}`);
|
||||
}
|
||||
|
||||
// load existing typing information from the cache
|
||||
// load existing typing information from the cache
|
||||
if (req.cachePath) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Request specifies cache path '${req.cachePath}', loading cached information...`);
|
||||
@@ -146,8 +149,8 @@ namespace ts.server.typingsInstaller {
|
||||
req.projectRootPath,
|
||||
this.safeListPath,
|
||||
this.packageNameToTypingLocation,
|
||||
req.typingOptions,
|
||||
req.compilerOptions);
|
||||
req.typeAcquisition,
|
||||
req.unresolvedImports);
|
||||
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Finished typings discovery: ${JSON.stringify(discoverTypingsResult)}`);
|
||||
@@ -196,8 +199,9 @@ namespace ts.server.typingsInstaller {
|
||||
if (!packageName) {
|
||||
continue;
|
||||
}
|
||||
const typingFile = typingToFileName(cacheLocation, packageName, this.installTypingHost);
|
||||
const typingFile = typingToFileName(cacheLocation, packageName, this.installTypingHost, this.log);
|
||||
if (!typingFile) {
|
||||
this.missingTypingsSet[packageName] = true;
|
||||
continue;
|
||||
}
|
||||
const existingTypingFile = this.packageNameToTypingLocation[packageName];
|
||||
@@ -228,18 +232,28 @@ namespace ts.server.typingsInstaller {
|
||||
}
|
||||
const result: string[] = [];
|
||||
for (const typing of typingsToInstall) {
|
||||
if (this.missingTypingsSet[typing]) {
|
||||
if (this.missingTypingsSet[typing] || this.packageNameToTypingLocation[typing]) {
|
||||
continue;
|
||||
}
|
||||
const validationResult = validatePackageName(typing);
|
||||
if (validationResult === PackageNameValidationResult.Ok) {
|
||||
result.push(typing);
|
||||
if (typing in this.typesRegistry) {
|
||||
result.push(typing);
|
||||
}
|
||||
else {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Entry for package '${typing}' does not exist in local types registry - skipping...`);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// add typing name to missing set so we won't process it again
|
||||
this.missingTypingsSet[typing] = true;
|
||||
if (this.log.isEnabled()) {
|
||||
switch (validationResult) {
|
||||
case PackageNameValidationResult.EmptyName:
|
||||
this.log.writeLine(`Package name '${typing}' cannot be empty`);
|
||||
break;
|
||||
case PackageNameValidationResult.NameTooLong:
|
||||
this.log.writeLine(`Package name '${typing}' should be less than ${MaxPackageNameLength} characters`);
|
||||
break;
|
||||
@@ -262,19 +276,8 @@ namespace ts.server.typingsInstaller {
|
||||
return result;
|
||||
}
|
||||
|
||||
private installTypings(req: DiscoverTypings, cachePath: string, currentlyCachedTypings: string[], typingsToInstall: string[]) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Installing typings ${JSON.stringify(typingsToInstall)}`);
|
||||
}
|
||||
typingsToInstall = this.filterTypings(typingsToInstall);
|
||||
if (typingsToInstall.length === 0) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`All typings are known to be missing or invalid - no need to go any further`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const npmConfigPath = combinePaths(cachePath, "package.json");
|
||||
protected ensurePackageDirectoryExists(directory: string) {
|
||||
const npmConfigPath = combinePaths(directory, "package.json");
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Npm config file: ${npmConfigPath}`);
|
||||
}
|
||||
@@ -282,82 +285,82 @@ namespace ts.server.typingsInstaller {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Npm config file: '${npmConfigPath}' is missing, creating new one...`);
|
||||
}
|
||||
this.ensureDirectoryExists(cachePath, this.installTypingHost);
|
||||
this.ensureDirectoryExists(directory, this.installTypingHost);
|
||||
this.installTypingHost.writeFile(npmConfigPath, "{}");
|
||||
}
|
||||
|
||||
this.runInstall(cachePath, typingsToInstall, installedTypings => {
|
||||
// TODO: watch project directory
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Requested to install typings ${JSON.stringify(typingsToInstall)}, installed typings ${JSON.stringify(installedTypings)}`);
|
||||
}
|
||||
const installedPackages: Map<true> = createMap<true>();
|
||||
const installedTypingFiles: string[] = [];
|
||||
for (const t of installedTypings) {
|
||||
const packageName = getBaseFileName(t);
|
||||
if (!packageName) {
|
||||
continue;
|
||||
}
|
||||
installedPackages[packageName] = true;
|
||||
const typingFile = typingToFileName(cachePath, packageName, this.installTypingHost);
|
||||
if (!typingFile) {
|
||||
continue;
|
||||
}
|
||||
if (!this.packageNameToTypingLocation[packageName]) {
|
||||
this.packageNameToTypingLocation[packageName] = typingFile;
|
||||
}
|
||||
installedTypingFiles.push(typingFile);
|
||||
}
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Installed typing files ${JSON.stringify(installedTypingFiles)}`);
|
||||
}
|
||||
for (const toInstall of typingsToInstall) {
|
||||
if (!installedPackages[toInstall]) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`New missing typing package '${toInstall}'`);
|
||||
}
|
||||
this.missingTypingsSet[toInstall] = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.sendResponse(this.createSetTypings(req, currentlyCachedTypings.concat(installedTypingFiles)));
|
||||
});
|
||||
}
|
||||
|
||||
private runInstall(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void {
|
||||
const requestId = this.installRunCount;
|
||||
|
||||
this.installRunCount++;
|
||||
let execInstallCmdCount = 0;
|
||||
const filteredTypings: string[] = [];
|
||||
for (const typing of typingsToInstall) {
|
||||
execNpmViewTyping(this, typing);
|
||||
private installTypings(req: DiscoverTypings, cachePath: string, currentlyCachedTypings: string[], typingsToInstall: string[]) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Installing typings ${JSON.stringify(typingsToInstall)}`);
|
||||
}
|
||||
|
||||
function execNpmViewTyping(self: TypingsInstaller, typing: string) {
|
||||
const command = `${self.npmPath} view @types/${typing} --silent name`;
|
||||
self.execAsync(NpmViewRequest, requestId, command, cachePath, (err, stdout, stderr) => {
|
||||
if (stdout) {
|
||||
filteredTypings.push(typing);
|
||||
}
|
||||
execInstallCmdCount++;
|
||||
if (execInstallCmdCount === typingsToInstall.length) {
|
||||
installFilteredTypings(self, filteredTypings);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function installFilteredTypings(self: TypingsInstaller, filteredTypings: string[]) {
|
||||
if (filteredTypings.length === 0) {
|
||||
postInstallAction([]);
|
||||
return;
|
||||
const filteredTypings = this.filterTypings(typingsToInstall);
|
||||
const scopedTypings = filteredTypings.map(x => `@types/${x}`);
|
||||
if (scopedTypings.length === 0) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`All typings are known to be missing or invalid - no need to go any further`);
|
||||
}
|
||||
const scopedTypings = filteredTypings.map(t => "@types/" + t);
|
||||
const command = `${self.npmPath} install ${scopedTypings.join(" ")} --save-dev`;
|
||||
self.execAsync(NpmInstallRequest, requestId, command, cachePath, (err, stdout, stderr) => {
|
||||
postInstallAction(stdout ? scopedTypings : []);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.ensurePackageDirectoryExists(cachePath);
|
||||
|
||||
const requestId = this.installRunCount;
|
||||
this.installRunCount++;
|
||||
|
||||
// send progress event
|
||||
this.sendResponse(<BeginInstallTypes>{
|
||||
kind: EventBeginInstallTypes,
|
||||
eventId: requestId,
|
||||
typingsInstallerVersion: ts.version, // qualified explicitly to prevent occasional shadowing
|
||||
projectName: req.projectName
|
||||
});
|
||||
|
||||
this.installTypingsAsync(requestId, scopedTypings, cachePath, ok => {
|
||||
try {
|
||||
if (!ok) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`install request failed, marking packages as missing to prevent repeated requests: ${JSON.stringify(filteredTypings)}`);
|
||||
}
|
||||
for (const typing of filteredTypings) {
|
||||
this.missingTypingsSet[typing] = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: watch project directory
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Installed typings ${JSON.stringify(scopedTypings)}`);
|
||||
}
|
||||
const installedTypingFiles: string[] = [];
|
||||
for (const packageName of filteredTypings) {
|
||||
const typingFile = typingToFileName(cachePath, packageName, this.installTypingHost, this.log);
|
||||
if (!typingFile) {
|
||||
this.missingTypingsSet[packageName] = true;
|
||||
continue;
|
||||
}
|
||||
if (!this.packageNameToTypingLocation[packageName]) {
|
||||
this.packageNameToTypingLocation[packageName] = typingFile;
|
||||
}
|
||||
installedTypingFiles.push(typingFile);
|
||||
}
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Installed typing files ${JSON.stringify(installedTypingFiles)}`);
|
||||
}
|
||||
|
||||
this.sendResponse(this.createSetTypings(req, currentlyCachedTypings.concat(installedTypingFiles)));
|
||||
}
|
||||
finally {
|
||||
this.sendResponse(<EndInstallTypes>{
|
||||
kind: EventEndInstallTypes,
|
||||
eventId: requestId,
|
||||
projectName: req.projectName,
|
||||
packagesToInstall: scopedTypings,
|
||||
installSuccess: ok,
|
||||
typingsInstallerVersion: ts.version // qualified explicitly to prevent occasional shadowing
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ensureDirectoryExists(directory: string, host: InstallTypingHost): void {
|
||||
@@ -385,9 +388,11 @@ namespace ts.server.typingsInstaller {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Got FS notification for ${f}, handler is already invoked '${isInvoked}'`);
|
||||
}
|
||||
this.sendResponse({ projectName: projectName, kind: "invalidate" });
|
||||
isInvoked = true;
|
||||
});
|
||||
if (!isInvoked) {
|
||||
this.sendResponse({ projectName: projectName, kind: server.ActionInvalidate });
|
||||
isInvoked = true;
|
||||
}
|
||||
}, /*pollingInterval*/ 2000);
|
||||
watchers.push(w);
|
||||
}
|
||||
this.projectWatchers[projectName] = watchers;
|
||||
@@ -396,15 +401,16 @@ namespace ts.server.typingsInstaller {
|
||||
private createSetTypings(request: DiscoverTypings, typings: string[]): SetTypings {
|
||||
return {
|
||||
projectName: request.projectName,
|
||||
typingOptions: request.typingOptions,
|
||||
typeAcquisition: request.typeAcquisition,
|
||||
compilerOptions: request.compilerOptions,
|
||||
typings,
|
||||
kind: "set"
|
||||
unresolvedImports: request.unresolvedImports,
|
||||
kind: ActionSet
|
||||
};
|
||||
}
|
||||
|
||||
private execAsync(requestKind: RequestKind, requestId: number, command: string, cwd: string, onRequestCompleted: RequestCompletedAction): void {
|
||||
this.pendingRunRequests.unshift({ requestKind, requestId, command, cwd, onRequestCompleted });
|
||||
private installTypingsAsync(requestId: number, args: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void {
|
||||
this.pendingRunRequests.unshift({ requestId, args, cwd, onRequestCompleted });
|
||||
this.executeWithThrottling();
|
||||
}
|
||||
|
||||
@@ -412,15 +418,15 @@ namespace ts.server.typingsInstaller {
|
||||
while (this.inFlightRequestCount < this.throttleLimit && this.pendingRunRequests.length) {
|
||||
this.inFlightRequestCount++;
|
||||
const request = this.pendingRunRequests.pop();
|
||||
this.runCommand(request.requestKind, request.requestId, request.command, request.cwd, (err, stdout, stderr) => {
|
||||
this.installWorker(request.requestId, request.args, request.cwd, ok => {
|
||||
this.inFlightRequestCount--;
|
||||
request.onRequestCompleted(err, stdout, stderr);
|
||||
request.onRequestCompleted(ok);
|
||||
this.executeWithThrottling();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract runCommand(requestKind: RequestKind, requestId: number, command: string, cwd: string, onRequestCompleted: RequestCompletedAction): void;
|
||||
protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings): void;
|
||||
protected abstract installWorker(requestId: number, args: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void;
|
||||
protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes): void;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
/// <reference path="types.d.ts" />
|
||||
/// <reference path="types.d.ts" />
|
||||
/// <reference path="shared.ts" />
|
||||
|
||||
namespace ts.server {
|
||||
export enum LogLevel {
|
||||
@@ -45,12 +46,13 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
export function createInstallTypingsRequest(project: Project, typingOptions: TypingOptions, cachePath?: string): DiscoverTypings {
|
||||
export function createInstallTypingsRequest(project: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>, cachePath?: string): DiscoverTypings {
|
||||
return {
|
||||
projectName: project.getProjectName(),
|
||||
fileNames: project.getFileNames(),
|
||||
fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true),
|
||||
compilerOptions: project.getCompilerOptions(),
|
||||
typingOptions,
|
||||
typeAcquisition,
|
||||
unresolvedImports,
|
||||
projectRootPath: getProjectRootPath(project),
|
||||
cachePath,
|
||||
kind: "discover"
|
||||
@@ -76,6 +78,7 @@ namespace ts.server {
|
||||
newLineCharacter: host.newLine || "\n",
|
||||
convertTabsToSpaces: true,
|
||||
indentStyle: ts.IndentStyle.Smart,
|
||||
insertSpaceAfterConstructor: false,
|
||||
insertSpaceAfterCommaDelimiter: true,
|
||||
insertSpaceAfterSemicolonInForStatements: true,
|
||||
insertSpaceBeforeAndAfterBinaryOperators: true,
|
||||
@@ -83,8 +86,10 @@ namespace ts.server {
|
||||
insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
|
||||
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
|
||||
insertSpaceBeforeFunctionParenthesis: false,
|
||||
placeOpenBraceOnNewLineForFunctions: false,
|
||||
placeOpenBraceOnNewLineForControlBlocks: false,
|
||||
};
|
||||
@@ -157,64 +162,6 @@ namespace ts.server {
|
||||
}
|
||||
};
|
||||
}
|
||||
function throwLanguageServiceIsDisabledError(): never {
|
||||
throw new Error("LanguageService is disabled");
|
||||
}
|
||||
|
||||
export const nullLanguageService: LanguageService = {
|
||||
cleanupSemanticCache: throwLanguageServiceIsDisabledError,
|
||||
getSyntacticDiagnostics: throwLanguageServiceIsDisabledError,
|
||||
getSemanticDiagnostics: throwLanguageServiceIsDisabledError,
|
||||
getCompilerOptionsDiagnostics: throwLanguageServiceIsDisabledError,
|
||||
getSyntacticClassifications: throwLanguageServiceIsDisabledError,
|
||||
getEncodedSyntacticClassifications: throwLanguageServiceIsDisabledError,
|
||||
getSemanticClassifications: throwLanguageServiceIsDisabledError,
|
||||
getEncodedSemanticClassifications: throwLanguageServiceIsDisabledError,
|
||||
getCompletionsAtPosition: throwLanguageServiceIsDisabledError,
|
||||
findReferences: throwLanguageServiceIsDisabledError,
|
||||
getCompletionEntryDetails: throwLanguageServiceIsDisabledError,
|
||||
getQuickInfoAtPosition: throwLanguageServiceIsDisabledError,
|
||||
findRenameLocations: throwLanguageServiceIsDisabledError,
|
||||
getNameOrDottedNameSpan: throwLanguageServiceIsDisabledError,
|
||||
getBreakpointStatementAtPosition: throwLanguageServiceIsDisabledError,
|
||||
getBraceMatchingAtPosition: throwLanguageServiceIsDisabledError,
|
||||
getSignatureHelpItems: throwLanguageServiceIsDisabledError,
|
||||
getDefinitionAtPosition: throwLanguageServiceIsDisabledError,
|
||||
getRenameInfo: throwLanguageServiceIsDisabledError,
|
||||
getTypeDefinitionAtPosition: throwLanguageServiceIsDisabledError,
|
||||
getReferencesAtPosition: throwLanguageServiceIsDisabledError,
|
||||
getDocumentHighlights: throwLanguageServiceIsDisabledError,
|
||||
getOccurrencesAtPosition: throwLanguageServiceIsDisabledError,
|
||||
getNavigateToItems: throwLanguageServiceIsDisabledError,
|
||||
getNavigationBarItems: throwLanguageServiceIsDisabledError,
|
||||
getNavigationTree: throwLanguageServiceIsDisabledError,
|
||||
getOutliningSpans: throwLanguageServiceIsDisabledError,
|
||||
getTodoComments: throwLanguageServiceIsDisabledError,
|
||||
getIndentationAtPosition: throwLanguageServiceIsDisabledError,
|
||||
getFormattingEditsForRange: throwLanguageServiceIsDisabledError,
|
||||
getFormattingEditsForDocument: throwLanguageServiceIsDisabledError,
|
||||
getFormattingEditsAfterKeystroke: throwLanguageServiceIsDisabledError,
|
||||
getDocCommentTemplateAtPosition: throwLanguageServiceIsDisabledError,
|
||||
isValidBraceCompletionAtPosition: throwLanguageServiceIsDisabledError,
|
||||
getEmitOutput: throwLanguageServiceIsDisabledError,
|
||||
getProgram: throwLanguageServiceIsDisabledError,
|
||||
getNonBoundSourceFile: throwLanguageServiceIsDisabledError,
|
||||
dispose: throwLanguageServiceIsDisabledError,
|
||||
getCompletionEntrySymbol: throwLanguageServiceIsDisabledError,
|
||||
getImplementationAtPosition: throwLanguageServiceIsDisabledError,
|
||||
getSourceFile: throwLanguageServiceIsDisabledError,
|
||||
getCodeFixesAtPosition: throwLanguageServiceIsDisabledError
|
||||
};
|
||||
|
||||
export interface ServerLanguageServiceHost {
|
||||
setCompilationSettings(options: CompilerOptions): void;
|
||||
notifyFileRemoved(info: ScriptInfo): void;
|
||||
}
|
||||
|
||||
export const nullLanguageServiceHost: ServerLanguageServiceHost = {
|
||||
setCompilationSettings: () => undefined,
|
||||
notifyFileRemoved: () => undefined
|
||||
};
|
||||
|
||||
export interface ProjectOptions {
|
||||
/**
|
||||
@@ -227,7 +174,7 @@ namespace ts.server {
|
||||
files?: string[];
|
||||
wildcardDirectories?: Map<WatchDirectoryFlags>;
|
||||
compilerOptions?: CompilerOptions;
|
||||
typingOptions?: TypingOptions;
|
||||
typeAcquisition?: TypeAcquisition;
|
||||
compileOnSave?: boolean;
|
||||
}
|
||||
|
||||
@@ -240,6 +187,11 @@ namespace ts.server {
|
||||
return `/dev/null/inferredProject${counter}*`;
|
||||
}
|
||||
|
||||
export function toSortedReadonlyArray(arr: string[]): SortedReadonlyArray<string> {
|
||||
arr.sort();
|
||||
return <any>arr;
|
||||
}
|
||||
|
||||
export class ThrottledOperations {
|
||||
private pendingTimeouts: Map<any> = createMap<any>();
|
||||
constructor(private readonly host: ServerHost) {
|
||||
|
||||
Reference in New Issue
Block a user