mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 21:53:42 -06:00
287 lines
12 KiB
TypeScript
287 lines
12 KiB
TypeScript
/// <reference path="types.d.ts" />
|
|
|
|
namespace ts.server {
|
|
export enum LogLevel {
|
|
terse,
|
|
normal,
|
|
requestTime,
|
|
verbose
|
|
}
|
|
|
|
export const emptyArray: ReadonlyArray<any> = [];
|
|
|
|
export interface Logger {
|
|
close(): void;
|
|
hasLevel(level: LogLevel): boolean;
|
|
loggingEnabled(): boolean;
|
|
perftrc(s: string): void;
|
|
info(s: string): void;
|
|
startGroup(): void;
|
|
endGroup(): void;
|
|
msg(s: string, type?: Msg.Types): void;
|
|
getLogFileName(): string;
|
|
}
|
|
|
|
export namespace Msg {
|
|
export type Err = "Err";
|
|
export const Err: Err = "Err";
|
|
export type Info = "Info";
|
|
export const Info: Info = "Info";
|
|
export type Perf = "Perf";
|
|
export const Perf: Perf = "Perf";
|
|
export type Types = Err | Info | Perf;
|
|
}
|
|
|
|
function getProjectRootPath(project: Project): Path {
|
|
switch (project.projectKind) {
|
|
case ProjectKind.Configured:
|
|
return <Path>getDirectoryPath(project.getProjectName());
|
|
case ProjectKind.Inferred:
|
|
// TODO: fixme
|
|
return <Path>"";
|
|
case ProjectKind.External:
|
|
const projectName = normalizeSlashes(project.getProjectName());
|
|
return project.projectService.host.fileExists(projectName) ? <Path>getDirectoryPath(projectName) : <Path>projectName;
|
|
}
|
|
}
|
|
|
|
export function createInstallTypingsRequest(project: Project, typingOptions: TypingOptions, cachePath?: string): DiscoverTypings {
|
|
return {
|
|
projectName: project.getProjectName(),
|
|
fileNames: project.getFileNames(),
|
|
compilerOptions: project.getCompilerOptions(),
|
|
typingOptions,
|
|
projectRootPath: getProjectRootPath(project),
|
|
cachePath,
|
|
kind: "discover"
|
|
};
|
|
}
|
|
|
|
export namespace Errors {
|
|
export function ThrowNoProject(): never {
|
|
throw new Error("No Project.");
|
|
}
|
|
export function ThrowProjectLanguageServiceDisabled(): never {
|
|
throw new Error("The project's language service is disabled.");
|
|
}
|
|
export function ThrowProjectDoesNotContainDocument(fileName: string, project: Project): never {
|
|
throw new Error(`Project '${project.getProjectName()}' does not contain document '${fileName}'`);
|
|
}
|
|
}
|
|
|
|
export function getDefaultFormatCodeSettings(host: ServerHost): FormatCodeSettings {
|
|
return {
|
|
indentSize: 4,
|
|
tabSize: 4,
|
|
newLineCharacter: host.newLine || "\n",
|
|
convertTabsToSpaces: true,
|
|
indentStyle: ts.IndentStyle.Smart,
|
|
insertSpaceAfterCommaDelimiter: true,
|
|
insertSpaceAfterSemicolonInForStatements: true,
|
|
insertSpaceBeforeAndAfterBinaryOperators: true,
|
|
insertSpaceAfterKeywordsInControlFlowStatements: true,
|
|
insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
|
|
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
|
|
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
|
|
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
|
|
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
|
|
placeOpenBraceOnNewLineForFunctions: false,
|
|
placeOpenBraceOnNewLineForControlBlocks: false,
|
|
};
|
|
}
|
|
|
|
export function mergeMaps(target: MapLike<any>, source: MapLike <any>): void {
|
|
for (const key in source) {
|
|
if (hasProperty(source, key)) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
export function removeItemFromSet<T>(items: T[], itemToRemove: T) {
|
|
if (items.length === 0) {
|
|
return;
|
|
}
|
|
const index = items.indexOf(itemToRemove);
|
|
if (index < 0) {
|
|
return;
|
|
}
|
|
if (index === items.length - 1) {
|
|
// last item - pop it
|
|
items.pop();
|
|
}
|
|
else {
|
|
// non-last item - replace it with the last one
|
|
items[index] = items.pop();
|
|
}
|
|
}
|
|
|
|
export type NormalizedPath = string & { __normalizedPathTag: any };
|
|
|
|
export function toNormalizedPath(fileName: string): NormalizedPath {
|
|
return <NormalizedPath>normalizePath(fileName);
|
|
}
|
|
|
|
export function normalizedPathToPath(normalizedPath: NormalizedPath, currentDirectory: string, getCanonicalFileName: (f: string) => string): Path {
|
|
const f = isRootedDiskPath(normalizedPath) ? normalizedPath : getNormalizedAbsolutePath(normalizedPath, currentDirectory);
|
|
return <Path>getCanonicalFileName(f);
|
|
}
|
|
|
|
export function asNormalizedPath(fileName: string): NormalizedPath {
|
|
return <NormalizedPath>fileName;
|
|
}
|
|
|
|
export interface NormalizedPathMap<T> {
|
|
get(path: NormalizedPath): T;
|
|
set(path: NormalizedPath, value: T): void;
|
|
contains(path: NormalizedPath): boolean;
|
|
remove(path: NormalizedPath): void;
|
|
}
|
|
|
|
export function createNormalizedPathMap<T>(): NormalizedPathMap<T> {
|
|
/* tslint:disable:no-null-keyword */
|
|
const map: Map<T> = Object.create(null);
|
|
/* tslint:enable:no-null-keyword */
|
|
return {
|
|
get(path) {
|
|
return map[path];
|
|
},
|
|
set(path, value) {
|
|
map[path] = value;
|
|
},
|
|
contains(path) {
|
|
return hasProperty(map, path);
|
|
},
|
|
remove(path) {
|
|
delete map[path];
|
|
}
|
|
};
|
|
}
|
|
function throwLanguageServiceIsDisabledError() {
|
|
throw new Error("LanguageService is disabled");
|
|
}
|
|
|
|
export const nullLanguageService: LanguageService = {
|
|
cleanupSemanticCache: (): any => throwLanguageServiceIsDisabledError(),
|
|
getSyntacticDiagnostics: (): any => throwLanguageServiceIsDisabledError(),
|
|
getSemanticDiagnostics: (): any => throwLanguageServiceIsDisabledError(),
|
|
getCompilerOptionsDiagnostics: (): any => throwLanguageServiceIsDisabledError(),
|
|
getSyntacticClassifications: (): any => throwLanguageServiceIsDisabledError(),
|
|
getEncodedSyntacticClassifications: (): any => throwLanguageServiceIsDisabledError(),
|
|
getSemanticClassifications: (): any => throwLanguageServiceIsDisabledError(),
|
|
getEncodedSemanticClassifications: (): any => throwLanguageServiceIsDisabledError(),
|
|
getCompletionsAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
|
findReferences: (): any => throwLanguageServiceIsDisabledError(),
|
|
getCompletionEntryDetails: (): any => throwLanguageServiceIsDisabledError(),
|
|
getQuickInfoAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
|
findRenameLocations: (): any => throwLanguageServiceIsDisabledError(),
|
|
getNameOrDottedNameSpan: (): any => throwLanguageServiceIsDisabledError(),
|
|
getBreakpointStatementAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
|
getBraceMatchingAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
|
getSignatureHelpItems: (): any => throwLanguageServiceIsDisabledError(),
|
|
getDefinitionAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
|
getRenameInfo: (): any => throwLanguageServiceIsDisabledError(),
|
|
getTypeDefinitionAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
|
getReferencesAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
|
getDocumentHighlights: (): any => throwLanguageServiceIsDisabledError(),
|
|
getOccurrencesAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
|
getNavigateToItems: (): any => throwLanguageServiceIsDisabledError(),
|
|
getNavigationBarItems: (): any => throwLanguageServiceIsDisabledError(),
|
|
getOutliningSpans: (): any => throwLanguageServiceIsDisabledError(),
|
|
getTodoComments: (): any => throwLanguageServiceIsDisabledError(),
|
|
getIndentationAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
|
getFormattingEditsForRange: (): any => throwLanguageServiceIsDisabledError(),
|
|
getFormattingEditsForDocument: (): any => throwLanguageServiceIsDisabledError(),
|
|
getFormattingEditsAfterKeystroke: (): any => throwLanguageServiceIsDisabledError(),
|
|
getDocCommentTemplateAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
|
isValidBraceCompletionAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
|
getEmitOutput: (): any => throwLanguageServiceIsDisabledError(),
|
|
getProgram: (): any => throwLanguageServiceIsDisabledError(),
|
|
getNonBoundSourceFile: (): any => throwLanguageServiceIsDisabledError(),
|
|
dispose: (): any => throwLanguageServiceIsDisabledError(),
|
|
getCompletionEntrySymbol: (): any => throwLanguageServiceIsDisabledError(),
|
|
getImplementationAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
|
getSourceFile: (): any => throwLanguageServiceIsDisabledError()
|
|
};
|
|
|
|
export interface ServerLanguageServiceHost {
|
|
setCompilationSettings(options: CompilerOptions): void;
|
|
notifyFileRemoved(info: ScriptInfo): void;
|
|
}
|
|
|
|
export const nullLanguageServiceHost: ServerLanguageServiceHost = {
|
|
setCompilationSettings: () => undefined,
|
|
notifyFileRemoved: () => undefined
|
|
};
|
|
|
|
export interface ProjectOptions {
|
|
/**
|
|
* true if config file explicitly listed files
|
|
**/
|
|
configHasFilesProperty?: boolean;
|
|
/**
|
|
* these fields can be present in the project file
|
|
**/
|
|
files?: string[];
|
|
wildcardDirectories?: Map<WatchDirectoryFlags>;
|
|
compilerOptions?: CompilerOptions;
|
|
typingOptions?: TypingOptions;
|
|
compileOnSave?: boolean;
|
|
}
|
|
|
|
export function isInferredProjectName(name: string) {
|
|
// POSIX defines /dev/null as a device - there should be no file with this prefix
|
|
return /dev\/null\/inferredProject\d+\*/.test(name);
|
|
}
|
|
|
|
export function makeInferredProjectName(counter: number) {
|
|
return `/dev/null/inferredProject${counter}*`;
|
|
}
|
|
|
|
export class ThrottledOperations {
|
|
private pendingTimeouts: Map<any> = createMap<any>();
|
|
constructor(private readonly host: ServerHost) {
|
|
}
|
|
|
|
public schedule(operationId: string, delay: number, cb: () => void) {
|
|
if (hasProperty(this.pendingTimeouts, operationId)) {
|
|
// another operation was already scheduled for this id - cancel it
|
|
this.host.clearTimeout(this.pendingTimeouts[operationId]);
|
|
}
|
|
// schedule new operation, pass arguments
|
|
this.pendingTimeouts[operationId] = this.host.setTimeout(ThrottledOperations.run, delay, this, operationId, cb);
|
|
}
|
|
|
|
private static run(self: ThrottledOperations, operationId: string, cb: () => void) {
|
|
delete self.pendingTimeouts[operationId];
|
|
cb();
|
|
}
|
|
}
|
|
|
|
export class GcTimer {
|
|
private timerId: any;
|
|
constructor(private readonly host: ServerHost, private readonly delay: number, private readonly logger: Logger) {
|
|
}
|
|
|
|
public scheduleCollect() {
|
|
if (!this.host.gc || this.timerId != undefined) {
|
|
// no global.gc or collection was already scheduled - skip this request
|
|
return;
|
|
}
|
|
this.timerId = this.host.setTimeout(GcTimer.run, this.delay, this);
|
|
}
|
|
|
|
private static run(self: GcTimer) {
|
|
self.timerId = undefined;
|
|
|
|
const log = self.logger.hasLevel(LogLevel.requestTime);
|
|
const before = log && self.host.getMemoryUsage();
|
|
|
|
self.host.gc();
|
|
if (log) {
|
|
const after = self.host.getMemoryUsage();
|
|
self.logger.perftrc(`GC::before ${before}, after ${after}`);
|
|
}
|
|
}
|
|
}
|
|
} |