From 99a87be883ab00f554e870b18be7db1f77c10d5b Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 23 Jan 2021 00:12:11 +0100 Subject: [PATCH] tsserver plugin --- packages/compiler/src/compiler/codegen.ts | 59 +- packages/compiler/src/syntax/codewriter.ts | 19 +- packages/compiler/src/syntax/index.ts | 1 + packages/compiler/src/syntax/reader.ts | 3 +- packages/compiler/src/syntax/scanner.ts | 12 +- packages/compiler/src/syntax/span.ts | 83 +++ packages/compiler/src/syntax/template.ts | 4 +- packages/compiler/src/syntax/tokens.ts | 1 + .../examples/typescript/tsconfig.json | 5 +- packages/syndicatec/src/tsc.ts | 44 +- packages/syndicatec/src/tsserver-plugin.ts | 615 ++++++++++++++++++ 11 files changed, 753 insertions(+), 93 deletions(-) create mode 100644 packages/compiler/src/syntax/span.ts create mode 100644 packages/syndicatec/src/tsserver-plugin.ts diff --git a/packages/compiler/src/compiler/codegen.ts b/packages/compiler/src/compiler/codegen.ts index f6b60aa..bade8f7 100644 --- a/packages/compiler/src/compiler/codegen.ts +++ b/packages/compiler/src/compiler/codegen.ts @@ -1,9 +1,9 @@ import { isToken, isTokenType, replace, commaJoin, startPos, fixPos, joinItems, - anonymousTemplate, laxRead, + anonymousTemplate, laxRead, itemText, - Items, Pattern, Templates, Substitution, TokenType, Pos, - SourceMap, CodeWriter, TemplateFunction, Token, itemText, + Items, Pattern, Templates, Substitution, TokenType, + SourceMap, CodeWriter, TemplateFunction, Token, SpanIndex, } from '../syntax/index.js'; import { SyndicateParser, SyndicateTypedParser, @@ -41,16 +41,11 @@ export interface CompileOptions { typescript?: boolean, } -// Essentially the same as a SourceMap, but indexed differently -// (originally for use with the TypeScript compiler). -export interface SourcePositionIndex { - sourcePositionAt(pos: number): Pos; -} - export interface CompilerOutput { text: string, map: SourceMap, - positionIndex: SourcePositionIndex, + targetToSourceMap: SpanIndex; + sourceToTargetMap: SpanIndex; } function receiverFor(s: FacetAction): Substitution { @@ -83,10 +78,9 @@ export class ExpansionContext { } get collectedFields(): FacetFields { - if (this._collectedFields === null) { - throw new Error("Internal error: this.collectedFields === null"); - } - return this._collectedFields; + // Allocates a transient array for collected fields in + // contexts lacking a surrounding collector - that is, for errors. + return this._collectedFields ?? []; } collectField(f: Binder) { @@ -361,40 +355,13 @@ export function compile(options: CompileOptions): CompilerOutput { const cw = new CodeWriter(inputFilename); cw.emit(tree); - const positionMap = cw.positionMap; + + const text = cw.text; return { - text: cw.text, + text, map: cw.map, - positionIndex: { - sourcePositionAt(pos: number): Pos { - if (positionMap.length === 0) return start; - - let lo = 0; - let hi = positionMap.length; - - // console.log(`\nsearching for ${pos}`); - while (true) { - if (lo === hi) { - const e = positionMap[lo - 1] ?? [0, start]; - if (e[0] > pos) throw new Error(); - if (positionMap[lo]?.[0] <= pos) throw new Error(); - // console.log(`found ${JSON.stringify(e)}`); - return e[1]; - } - - const mid = (lo + hi) >> 1; - const e = positionMap[mid]; - - // console.log(`${pos} lo ${lo} hi ${hi} mid ${mid} probe ${JSON.stringify([e[0], e[1].pos])}`); - - if (e[0] <= pos) { - lo = mid + 1; - } else { - hi = mid; - } - } - } - } + targetToSourceMap: cw.targetToSourceMap.index(), + sourceToTargetMap: cw.sourceToTargetMap.index(), }; } diff --git a/packages/compiler/src/syntax/codewriter.ts b/packages/compiler/src/syntax/codewriter.ts index da5c531..c63b916 100644 --- a/packages/compiler/src/syntax/codewriter.ts +++ b/packages/compiler/src/syntax/codewriter.ts @@ -1,6 +1,7 @@ -import { TokenType, Item, Items, isGroup } from './tokens.js'; +import { Token, TokenType, Item, Items, isGroup } from './tokens.js'; import { Pos, startPos, advancePos } from './position.js'; import { vlqEncode } from './vlq.js'; +import { SpanInfo } from './span.js'; export interface SourceMap { version: 3; @@ -51,7 +52,8 @@ export class CodeWriter { readonly sources: Array = []; readonly chunks: Array = []; readonly mappings: Array> = []; - readonly positionMap: Array<[number, Pos]> = []; + readonly targetToSourceMap = new SpanInfo(); + readonly sourceToTargetMap = new SpanInfo(); previous: Partial = {}; previousPos: Pos | null = null; @@ -145,24 +147,14 @@ export class CodeWriter { this.mappings[this.mappings.length - 1].push(n); } - augmentPositionMap(p: Pos) { - if (this.positionMap.length > 0) { - const prev = this.positionMap[this.positionMap.length - 1][1]; - if ((prev.name === p.name) && (prev.pos === p.pos)) return; - } - this.positionMap.push([this.pos.pos, { ... p }]); - } - chunk(p: Pos, s: string, type: TokenType) { p = { ... p }; this.chunks.push(s); - this.augmentPositionMap(p); if (this.mappings.length === 0) this.finishLine(); this.addMapping(p, type); for (const ch of s) { advancePos(p, ch); if (advancePos(this.pos, ch)) { - this.augmentPositionMap(p); this.finishLine(); this.addMapping(p, type); } @@ -179,7 +171,10 @@ export class CodeWriter { } else if (i === null) { // Do nothing. } else { + const targetStart = this.pos.pos; + if (!i.synthetic) this.sourceToTargetMap.add(i.start.pos, i.end.pos, targetStart); this.chunk(i.start, i.text, i.type); + this.targetToSourceMap.add(targetStart, this.pos.pos, i); } } } diff --git a/packages/compiler/src/syntax/index.ts b/packages/compiler/src/syntax/index.ts index 556eb87..db10f2c 100644 --- a/packages/compiler/src/syntax/index.ts +++ b/packages/compiler/src/syntax/index.ts @@ -4,6 +4,7 @@ export * from './matcher.js'; export * from './position.js'; export * from './reader.js'; export * from './scanner.js'; +export * from './span.js'; export * from './template.js'; export * from './tokens.js'; export * from './vlq.js'; diff --git a/packages/compiler/src/syntax/reader.ts b/packages/compiler/src/syntax/reader.ts index ed18b94..6b538a9 100644 --- a/packages/compiler/src/syntax/reader.ts +++ b/packages/compiler/src/syntax/reader.ts @@ -128,11 +128,12 @@ export interface LaxReadOptions { start?: Pos, name?: string, extraDelimiters?: string, + synthetic?: boolean, } export function laxRead(source: string, options: LaxReadOptions = {}): Items { const start = options.start ?? startPos(options.name ?? null); - const scanner = new StringScanner(start, source); + const scanner = new StringScanner(start, source, options.synthetic); if (options.extraDelimiters) scanner.addDelimiters(options.extraDelimiters); const reader = new LaxReader(scanner); return reader.readToEnd(); diff --git a/packages/compiler/src/syntax/scanner.ts b/packages/compiler/src/syntax/scanner.ts index 69343bf..83a7d1d 100644 --- a/packages/compiler/src/syntax/scanner.ts +++ b/packages/compiler/src/syntax/scanner.ts @@ -3,12 +3,14 @@ import { Pos, advancePos } from './position.js'; export abstract class Scanner implements IterableIterator { readonly pos: Pos; + readonly synthetic: boolean | undefined; charBuffer: string | null = null; tokenBuffer: Token | null = null; delimiters = ' \t\n\r\'"`.,;()[]{}/'; - constructor(pos: Pos) { + constructor(pos: Pos, synthetic?: boolean) { this.pos = { ... pos }; + this.synthetic = synthetic; } [Symbol.iterator](): IterableIterator { @@ -40,11 +42,11 @@ export abstract class Scanner implements IterableIterator { } makeToken(start: Pos, type: TokenType, text: string): Token { - return { type, start, end: this.mark(), text }; + return { type, start, end: this.mark(), text, ... this.synthetic && { synthetic: true } }; } makeGroupInProgress(open: Token, items: Array = []): GroupInProgress { - return { start: open.start, open, close: null, items }; + return { start: open.start, open, close: null, items, ... this.synthetic && { synthetic: true } }; } mark(): Pos { @@ -211,8 +213,8 @@ export class StringScanner extends Scanner { readonly input: string; index: number; - constructor(pos: Pos, input: string) { - super(pos); + constructor(pos: Pos, input: string, synthetic?: boolean) { + super(pos, synthetic); this.input = input; this.index = 0; } diff --git a/packages/compiler/src/syntax/span.ts b/packages/compiler/src/syntax/span.ts new file mode 100644 index 0000000..d2dcdd5 --- /dev/null +++ b/packages/compiler/src/syntax/span.ts @@ -0,0 +1,83 @@ +export class SpanResult { + readonly searchTarget: number; + readonly start: number; + readonly items: Array<{ end: number, item: T }> = []; + + constructor(searchTarget: number, start: number) { + this.searchTarget = searchTarget; + this.start = start; + } + + get offset(): number { + return this.searchTarget - this.start; + } + + get firstItem(): T { + return this.items[0].item; + } + + get lastItem(): T { + return this.items[this.items.length - 1].item; + } +} + +export class SpanIndex { + readonly index: Array<[number, Array<[number, T]>]> = []; + + get(pos: number): SpanResult | null { + if (this.index.length === 0) return null; + + let lo = 0; + let hi = this.index.length; + + // console.log(`\nsearching for ${target}`); + while (true) { + if (lo === hi) { + if (lo === 0) return null; + const e = this.index[lo - 1]; + if (e[0] > pos) throw new Error("INTERNAL ERROR: bad binary search (1)"); + if (this.index[lo]?.[0] <= pos) throw new Error("INTERNAL ERROR: bad binary search (2)"); + // console.log(`found ${JSON.stringify(e)}, ${JSON.stringify(items[lo] ?? null)}`); + const r = new SpanResult(pos, e[0]); + e[1].forEach(([end, item]) => { + if (pos < end) { + r.items.push({ end, item }); + } + }); + return (r.items.length > 0) ? r : null; + } + + const mid = (lo + hi) >> 1; + const e = this.index[mid]; + + // console.log(`${target} lo ${lo} hi ${hi} mid ${mid} probe ${JSON.stringify([e[0], e[1].target])}`); + + if (e[0] <= pos) { + lo = mid + 1; + } else { + hi = mid; + } + } + } +} + +export class SpanInfo { + readonly spans: Map> = new Map(); + + add(start: number, end: number, t: T) { + if (!this.spans.has(start)) { + this.spans.set(start, []); + } + this.spans.get(start)!.push([end, t]); + } + + index(): SpanIndex { + const i = new SpanIndex(); + this.spans.forEach((ends, start) => { + ends.sort((a, b) => a[0] - b[0]); + i.index.push([start, ends]); + }); + i.index.sort((a, b) => a[0] - b[0]); + return i; + } +} diff --git a/packages/compiler/src/syntax/template.ts b/packages/compiler/src/syntax/template.ts index 8031e19..772888f 100644 --- a/packages/compiler/src/syntax/template.ts +++ b/packages/compiler/src/syntax/template.ts @@ -10,7 +10,7 @@ const substPat = M.scope((o: { pos: Pos }) => export type Substitution = Items | string; function toItems(s: Substitution, pos: Pos): Items { - return typeof s === 'string' ? laxRead(s, { start: pos }) : s; + return typeof s === 'string' ? laxRead(s, { start: pos, synthetic: true }) : s; } export type TemplateFunction = (consts: TemplateStringsArray, ... vars: Substitution[]) => Items; @@ -42,7 +42,7 @@ export class Templates { } } let i = 0; - return M.replace(laxRead(source, { start, extraDelimiters: '$' }), + return M.replace(laxRead(source, { start, extraDelimiters: '$', synthetic: true }), substPat, sub => toItems(vars[i++], sub.pos)); }; diff --git a/packages/compiler/src/syntax/tokens.ts b/packages/compiler/src/syntax/tokens.ts index 28e73c8..9d26c22 100644 --- a/packages/compiler/src/syntax/tokens.ts +++ b/packages/compiler/src/syntax/tokens.ts @@ -12,6 +12,7 @@ export enum TokenType { export interface TokenBase { start: Pos; end: Pos; + synthetic?: boolean; // default: false } export interface Token extends TokenBase { diff --git a/packages/syndicatec/examples/typescript/tsconfig.json b/packages/syndicatec/examples/typescript/tsconfig.json index 983aa20..36a2864 100644 --- a/packages/syndicatec/examples/typescript/tsconfig.json +++ b/packages/syndicatec/examples/typescript/tsconfig.json @@ -11,7 +11,10 @@ "moduleResolution": "node", "module": "es6", "sourceMap": true, - "strict": true + "strict": true, + "plugins": [ + { "name": "@syndicate-lang/syndicatec/lib/tsserver-plugin.js" } + ] }, "include": ["src/**/*"] } diff --git a/packages/syndicatec/src/tsc.ts b/packages/syndicatec/src/tsc.ts index 122a297..7f9214d 100644 --- a/packages/syndicatec/src/tsc.ts +++ b/packages/syndicatec/src/tsc.ts @@ -2,7 +2,7 @@ import ts from 'typescript'; import crypto from 'crypto'; import { compile } from '@syndicate-lang/compiler'; -import { SourcePositionIndex } from '@syndicate-lang/compiler/lib/compiler/codegen'; +import { SpanIndex, Token } from '@syndicate-lang/compiler/lib/syntax'; function reportDiagnostic(diagnostic: ts.Diagnostic) { if (diagnostic.file) { @@ -22,7 +22,8 @@ function reportErrorSummary(n: number) { interface SyndicateInfo { originalSource: string; - positionIndex: SourcePositionIndex; + targetToSourceMap: SpanIndex; + sourceToTargetMap: SpanIndex; } const syndicateInfo: Map = new Map(); @@ -56,19 +57,21 @@ function createProgram(rootNames: readonly string[] | undefined, onError?.(`Could not read input file ${fileName}`); return undefined; } - const { text: expandedText, positionIndex } = compile({ + const { text: expandedText, targetToSourceMap, sourceToTargetMap } = compile({ source: inputText, name: fileName, typescript: true, }); syndicateInfo.set(fileName, { originalSource: inputText, - positionIndex, + targetToSourceMap, + sourceToTargetMap, }); const sf = ts.createSourceFile(fileName, expandedText, languageVersion, true); (sf as any).version = crypto.createHash('sha256').update(expandedText).digest('hex'); return sf; } catch (e) { + console.error(e); onError?.(e.message); return undefined; } @@ -85,36 +88,25 @@ function createProgram(rootNames: readonly string[] | undefined, projectReferences); } -export function fixSourceMap(ctx: ts.TransformationContext): ts.Transformer { +export function fixSourceMap(_ctx: ts.TransformationContext): ts.Transformer { return sf => { const fileName = sf.fileName; const info = syndicateInfo.get(fileName); if (info === void 0) throw new Error("No Syndicate info available for " + fileName); - const positionIndex = info.positionIndex; - - // console.log('fixSourceMap', fileName, sf.text.length, info.originalSource.length); - + const targetToSourceMap = info.targetToSourceMap; const syndicateSource = ts.createSourceMapSource(fileName, info.originalSource); - const expandedSource = ts.createSourceMapSource(fileName + '.expanded', sf.text); function adjustSourceMap(n: ts.Node) { - const ps = positionIndex.sourcePositionAt(n.pos); - const pe = positionIndex.sourcePositionAt(n.end); - if (ps.name === fileName && pe.name === fileName) { - ts.setSourceMapRange(n, { pos: ps.pos, end: pe.pos, source: syndicateSource }); - // console.group(ts.SyntaxKind[n.kind], `${n.pos}-${n.end} ==> ${ps.pos}-${pe.pos}`); - } else if (ps.name === null && pe.name === null) { - ts.setSourceMapRange(n, { pos: ps.pos, end: pe.pos, source: expandedSource }); - // console.group(ts.SyntaxKind[n.kind], n.pos, 'synthetic'); - } else if (ps.name === null) { - ts.setSourceMapRange(n, { pos: pe.pos, end: pe.pos, source: expandedSource }); - // console.group(ts.SyntaxKind[n.kind], n.pos, 'mixed end'); - } else { - ts.setSourceMapRange(n, { pos: ps.pos, end: ps.pos, source: expandedSource }); - // console.group(ts.SyntaxKind[n.kind], n.pos, 'mixed start'); + const ps = targetToSourceMap.get(n.pos); + const pe = targetToSourceMap.get(n.end); + if (ps !== null && pe !== null) { + ts.setSourceMapRange(n, { + pos: ps.firstItem.start.pos + ps.offset, + end: pe.lastItem.start.pos + pe.offset, + source: syndicateSource, + }); } ts.forEachChild(n, adjustSourceMap); - // console.groupEnd(); } adjustSourceMap(sf); @@ -123,7 +115,7 @@ export function fixSourceMap(ctx: ts.TransformationContext): ts.Transformer { + + interface SyndicateInfo { + sourceFile: ts.SourceFile; + originalSource: string; + targetToSourceMap: Syntax.SpanIndex; + sourceToTargetMap: Syntax.SpanIndex; + } + + const syndicateInfo: Map = new Map(); + + const syndicateRootDirs: Set = new Set(); + + function getInfo(fileName: string): SyndicateInfo | undefined { + return syndicateInfo.get(fileName); + } + + function fixupDocumentSpan(loc: ts.DocumentSpan | undefined): ts.DocumentSpan | undefined { + if (loc !== void 0) { + withFileName(loc.fileName, () => void 0, (fixupLoc) => { + fixupLoc.span(loc.textSpan); + fixupLoc.span(loc.contextSpan); + }); + withFileName(loc.originalFileName, () => void 0, (fixupOriginal) => { + fixupOriginal.span(loc.originalTextSpan); + fixupOriginal.span(loc.originalContextSpan); + }); + } + return loc; + } + + class Fixup { + readonly info: SyndicateInfo; + + constructor(info: SyndicateInfo) { + this.info = info; + } + + loc(start: number): number | undefined { + const p = this.info.targetToSourceMap.get(start); + if (p === null) return undefined; + return p.firstItem.start.pos + p.offset; + } + + span(s: ts.TextSpan | undefined): ts.TextSpan | undefined { + if (s !== void 0) { + const newStart = this.loc(s.start); + if (newStart === void 0) throw new Error("Source position unavailable for TextSpan " + JSON.stringify(s)); + s.start = newStart; + } + return s; + } + + diagnostic(d: T, ds: T[]) { + if (d.start !== void 0) { + const p = this.info.targetToSourceMap.get(d.start); + if (p === null) return; + if (p.firstItem.synthetic) return; + d.start = p.firstItem.start.pos + p.offset; + } + ds.push(d); + } + + diagnostics(ds: T[]): T[] { + const vs: T[] = []; + ds.forEach(d => this.diagnostic(d, vs)); + return vs; + } + } + + class PositionFixup extends Fixup { + readonly target: Syntax.SpanResult; + + constructor(info: SyndicateInfo, target: Syntax.SpanResult) { + super(info); + this.target = target; + } + + get targetStart(): number { + return this.target.firstItem + this.target.offset; + } + } + + function withFileName(fileName: string | undefined, + kNoInfo: () => T, + k: (f: Fixup) => T): T + { + if (fileName === void 0) return kNoInfo(); + const info = getInfo(fileName); + if (info === void 0) return kNoInfo(); + return k(new Fixup(info)); + } + + function withPosition(fileName: string, + position: number, + kNoInfo: () => T, + kNoPosition: () => T, + k: (f: PositionFixup) => T): T + { + return withFileName(fileName, kNoInfo, (fx) => { + const t = fx.info.sourceToTargetMap.get(position); + if (t === null) return kNoPosition(); + return k(new PositionFixup(fx.info, t)); + }); + } + + function hookHost(host0: ts.CompilerHost | undefined, + options: ts.CompilerOptions) + { + const host = (host0 === void 0) ? ts.createCompilerHost(options, true) : host0; + + if ('Syndicate_hooked' in host) { + console.warn('Syndicate plugin refusing to hook CompilerHost twice'); + } else { + (host as any).Syndicate_hooked = true; + + const oldGetSourceFile = host.getSourceFile; + host.getSourceFile = getSourceFile; + + function getSourceFile(fileName: string, + languageVersion: ts.ScriptTarget, + onError?: ((message: string) => void), + shouldCreateNewSourceFile?: boolean): ts.SourceFile | undefined + { + let shouldExpand = false; + syndicateRootDirs.forEach(d => { + if (fileName.startsWith(d)) { + shouldExpand = true; + } + }); + if (shouldExpand) { + try { + const inputText = host.readFile(fileName); + if (inputText === void 0) { + onError?.(`Could not read input file ${fileName}`); + return undefined; + } + console.log('Syndicate compiling', fileName); + const { text: expandedText, targetToSourceMap, sourceToTargetMap } = compile({ + source: inputText, + name: fileName, + typescript: true, + }); + const sf = ts.createSourceFile(fileName, expandedText, languageVersion, true); + syndicateInfo.set(fileName, { + sourceFile: sf, + originalSource: inputText, + targetToSourceMap, + sourceToTargetMap, + }); + (sf as any).version = crypto.createHash('sha256').update(expandedText).digest('hex'); + return sf; + } catch (e) { + console.error(e); + onError?.(e.message); + return undefined; + } + } else { + return oldGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile); + } + } + } + + return host; + } + + { + const oldCreateProgram = ts.createProgram; + + function createProgram(createProgramOptions: ts.CreateProgramOptions): ts.Program; + function createProgram(rootNames: readonly string[], + options: ts.CompilerOptions, + host?: ts.CompilerHost, + oldProgram?: ts.Program, + configFileParsingDiagnostics?: readonly ts.Diagnostic[]): ts.Program; + function createProgram(rootNamesOrOptions: readonly string[] | ts.CreateProgramOptions, + options?: ts.CompilerOptions, + host?: ts.CompilerHost, + oldProgram?: ts.Program, + configFileParsingDiagnostics?: readonly ts.Diagnostic[]) + : ts.Program + { + if (Array.isArray(rootNamesOrOptions)) { + const rootNames = rootNamesOrOptions; + host = hookHost(host, options!); + return oldCreateProgram(rootNames, options!, host, oldProgram, configFileParsingDiagnostics); + } else { + const createProgramOptions = rootNamesOrOptions as ts.CreateProgramOptions; + createProgramOptions.host = + hookHost(createProgramOptions.host, createProgramOptions.options); + return oldCreateProgram(createProgramOptions); + } + } + + ts.createProgram = createProgram; + } + + class SyndicateLanguageService implements ts.LanguageService { + readonly inner: ts.LanguageService; + + constructor(inner: ts.LanguageService) { + this.inner = inner; + } + + cleanupSemanticCache(): void { + } + + getSyntacticDiagnostics(fileName: string): ts.DiagnosticWithLocation[] { + const ds = this.inner.getSyntacticDiagnostics(fileName); + return withFileName(fileName, () => ds, (fixup) => fixup.diagnostics(ds)); + } + + getSemanticDiagnostics(fileName: string): ts.Diagnostic[] { + const ds = this.inner.getSemanticDiagnostics(fileName); + return withFileName(fileName, () => ds, (fixup) => fixup.diagnostics(ds)); + } + + getSuggestionDiagnostics(fileName: string): ts.DiagnosticWithLocation[] { + const ds = this.inner.getSuggestionDiagnostics(fileName); + return withFileName(fileName, () => ds, (fixup) => fixup.diagnostics(ds)); + } + + getCompilerOptionsDiagnostics(): ts.Diagnostic[] { + return this.inner.getCompilerOptionsDiagnostics(); + } + + getSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[]; + getSyntacticClassifications(fileName: string, span: ts.TextSpan, format: ts.SemanticClassificationFormat): ts.ClassifiedSpan[] | ts.ClassifiedSpan2020[]; + getSyntacticClassifications(fileName: string, span: ts.TextSpan, format?: ts.SemanticClassificationFormat): ts.ClassifiedSpan[] | ts.ClassifiedSpan2020[] { + return withPosition( + fileName, span.start, + () => this.inner.getSyntacticClassifications(fileName, span, format!), + () => [], + (fixup) => { + const cs = this.inner.getSyntacticClassifications(fileName, span, format!); + cs.forEach((c: ts.ClassifiedSpan | ts.ClassifiedSpan2020) => fixup.span(c.textSpan)); + return cs; + }); + } + + getSemanticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[]; + getSemanticClassifications(fileName: string, span: ts.TextSpan, format: ts.SemanticClassificationFormat): ts.ClassifiedSpan[] | ts.ClassifiedSpan2020[]; + getSemanticClassifications(fileName: any, span: ts.TextSpan, format?: ts.SemanticClassificationFormat): ts.ClassifiedSpan[] | ts.ClassifiedSpan2020[] { + return withPosition( + fileName, span.start, + () => this.inner.getSemanticClassifications(fileName, span, format!), + () => [], + (fixup) => { + const cs = this.inner.getSemanticClassifications(fileName, span, format!); + cs.forEach((c: ts.ClassifiedSpan | ts.ClassifiedSpan2020) => fixup.span(c.textSpan)); + return cs; + }); + } + + getEncodedSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.Classifications { + return withPosition( + fileName, span.start, + () => this.inner.getEncodedSyntacticClassifications(fileName, span), + () => ({ spans: [], endOfLineState: ts.EndOfLineState.None }), + (fixup) => { + const cs = this.inner.getEncodedSyntacticClassifications(fileName, span); + for (let i = 0; i < cs.spans.length; i += 3) { + const newStart = fixup.loc(cs.spans[i]); + if (newStart === void 0) { + cs.spans.splice(i, 3); + } else { + cs.spans[i] = newStart; + } + } + return cs; + }); + } + + getEncodedSemanticClassifications(fileName: string, span: ts.TextSpan, format?: ts.SemanticClassificationFormat): ts.Classifications { + return withPosition( + fileName, span.start, + () => this.inner.getEncodedSemanticClassifications(fileName, span, format), + () => ({ spans: [], endOfLineState: ts.EndOfLineState.None }), + (fixup) => { + const cs = this.inner.getEncodedSemanticClassifications(fileName, span, format); + for (let i = 0; i < cs.spans.length; i += 3) { + const newStart = fixup.loc(cs.spans[i]); + if (newStart === void 0) { + cs.spans.splice(i, 3); + } else { + cs.spans[i] = newStart; + } + } + return cs; + }); + } + + getCompletionsAtPosition(fileName: string, position: number, options: ts.GetCompletionsAtPositionOptions | undefined): ts.WithMetadata | undefined { + return withPosition( + fileName, position, + () => this.inner.getCompletionsAtPosition(fileName, position, options), + () => void 0, + (fixup) => { + const cs = this.inner.getCompletionsAtPosition(fileName, fixup.targetStart, options); + if (cs !== void 0) { + fixup.span(cs.optionalReplacementSpan); + cs.entries.forEach(c => fixup.span(c.replacementSpan)); + } + return cs; + }); + } + + getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: ts.FormatCodeOptions | ts.FormatCodeSettings | undefined, source: string | undefined, preferences: ts.UserPreferences | undefined): ts.CompletionEntryDetails | undefined { + return withPosition( + fileName, position, + () => this.inner.getCompletionEntryDetails(fileName, position, entryName, formatOptions, source, preferences), + () => void 0, + (fixup) => { + const d = this.inner.getCompletionEntryDetails(fileName, fixup.targetStart, entryName, formatOptions, source, preferences); + if (d !== void 0) { + d.codeActions?.forEach(a => + a.changes.forEach(c => + c.textChanges.forEach(c => + fixup.span(c.span)))); + } + return d; + }); + } + + getCompletionEntrySymbol(fileName: string, position: number, name: string, source: string | undefined): ts.Symbol | undefined { + // TODO: hmm. Is this acceptable? + return void 0; + } + + getQuickInfoAtPosition(fileName: string, position: number): ts.QuickInfo | undefined { + return withPosition( + fileName, position, + () => this.inner.getQuickInfoAtPosition(fileName, position), + () => void 0, + (fixup) => { + const qi = this.inner.getQuickInfoAtPosition(fileName, fixup.targetStart); + if (qi !== void 0) fixup.span(qi.textSpan); + return qi; + }); + } + + getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): ts.TextSpan | undefined { + throw new Error('Method not implemented.'); + } + + getBreakpointStatementAtPosition(fileName: string, position: number): ts.TextSpan | undefined { + throw new Error('Method not implemented.'); + } + + getSignatureHelpItems(fileName: string, position: number, options: ts.SignatureHelpItemsOptions | undefined): ts.SignatureHelpItems | undefined { + return withPosition( + fileName, position, + () => this.inner.getSignatureHelpItems(fileName, position, options), + () => void 0, + (fixup) => { + const items = this.inner.getSignatureHelpItems(fileName, fixup.targetStart, options); + if (items !== void 0) fixup.span(items.applicableSpan); + return items; + }); + } + + getRenameInfo(fileName: string, position: number, options?: ts.RenameInfoOptions): ts.RenameInfo { + return withPosition( + fileName, position, + () => this.inner.getRenameInfo(fileName, position, options), + () => ({ canRename: false, localizedErrorMessage: 'Identifier not present in expanded source code' }), + (fixup) => { + const ri = this.inner.getRenameInfo(fileName, fixup.targetStart, options); + if (ri.canRename) { + fixup.span(ri.triggerSpan); + } + return ri; + }); + } + + findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): readonly ts.RenameLocation[] | undefined { + return withPosition( + fileName, position, + () => this.inner.findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename), + () => void 0, + (fixup) => { + const locs = this.inner.findRenameLocations(fileName, fixup.targetStart, findInStrings, findInComments, providePrefixAndSuffixTextForRename); + if (locs !== void 0) locs.forEach(fixupDocumentSpan); + return locs; + }); + } + + getSmartSelectionRange(fileName: string, position: number): ts.SelectionRange { + throw new Error('Method not implemented.'); + } + + getDefinitionAtPosition(fileName: string, position: number): readonly ts.DefinitionInfo[] | undefined { + return withPosition( + fileName, position, + () => this.inner.getDefinitionAtPosition(fileName, position), + () => undefined, + (fixup) => { + const dis = this.inner.getDefinitionAtPosition(fileName, fixup.targetStart); + if (dis !== void 0) dis.forEach(fixupDocumentSpan); + return dis; + }); + } + + getDefinitionAndBoundSpan(fileName: string, position: number): ts.DefinitionInfoAndBoundSpan | undefined { + throw new Error('Method not implemented.'); + } + + getTypeDefinitionAtPosition(fileName: string, position: number): readonly ts.DefinitionInfo[] | undefined { + throw new Error('Method not implemented.'); + } + + getImplementationAtPosition(fileName: string, position: number): readonly ts.ImplementationLocation[] | undefined { + throw new Error('Method not implemented.'); + } + + getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] | undefined { + throw new Error('Method not implemented.'); + } + + findReferences(fileName: string, position: number): ts.ReferencedSymbol[] | undefined { + throw new Error('Method not implemented.'); + } + + getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): ts.DocumentHighlights[] | undefined { + return withPosition( + fileName, position, + () => this.inner.getDocumentHighlights(fileName, position, filesToSearch), + () => [], + fixup => { + const dhs = this.inner.getDocumentHighlights(fileName, fixup.targetStart, filesToSearch); + if (dhs === void 0) return dhs; + dhs.forEach(dh => dh.highlightSpans.forEach((s: tslib.HighlightSpan) => { + fixup.span(s.textSpan); + fixup.span(s.contextSpan); + })); + return dhs; + }); + } + + getOccurrencesAtPosition(fileName: string, position: number): readonly ts.ReferenceEntry[] | undefined { + throw new Error('Method not implemented.'); + } + + getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): ts.NavigateToItem[] { + throw new Error('Method not implemented.'); + } + + getNavigationBarItems(fileName: string): ts.NavigationBarItem[] { + throw new Error('Method not implemented.'); + } + + getNavigationTree(fileName: string): ts.NavigationTree { + throw new Error('Method not implemented.'); + } + + prepareCallHierarchy(fileName: string, position: number): ts.CallHierarchyItem | ts.CallHierarchyItem[] | undefined { + throw new Error('Method not implemented.'); + } + + provideCallHierarchyIncomingCalls(fileName: string, position: number): ts.CallHierarchyIncomingCall[] { + throw new Error('Method not implemented.'); + } + + provideCallHierarchyOutgoingCalls(fileName: string, position: number): ts.CallHierarchyOutgoingCall[] { + throw new Error('Method not implemented.'); + } + + getOutliningSpans(fileName: string): ts.OutliningSpan[] { + throw new Error('Method not implemented.'); + } + + getTodoComments(fileName: string, descriptors: ts.TodoCommentDescriptor[]): ts.TodoComment[] { + throw new Error('Method not implemented.'); + } + + getBraceMatchingAtPosition(fileName: string, position: number): ts.TextSpan[] { + throw new Error('Method not implemented.'); + } + + getIndentationAtPosition(fileName: string, position: number, options: ts.EditorOptions | ts.EditorSettings): number { + throw new Error('Method not implemented.'); + } + + getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions | ts.FormatCodeSettings): ts.TextChange[] { + throw new Error('Method not implemented.'); + } + + getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions | ts.FormatCodeSettings): ts.TextChange[] { + throw new Error('Method not implemented.'); + } + + getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: ts.FormatCodeOptions | ts.FormatCodeSettings): ts.TextChange[] { + throw new Error('Method not implemented.'); + } + + getDocCommentTemplateAtPosition(fileName: string, position: number): ts.TextInsertion | undefined { + throw new Error('Method not implemented.'); + } + + isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { + throw new Error('Method not implemented.'); + } + + getJsxClosingTagAtPosition(fileName: string, position: number): ts.JsxClosingTagInfo | undefined { + throw new Error('Method not implemented.'); + } + + getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): ts.TextSpan | undefined { + throw new Error('Method not implemented.'); + } + + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: readonly number[], formatOptions: ts.FormatCodeSettings, preferences: ts.UserPreferences): readonly ts.CodeFixAction[] { + throw new Error('Method not implemented.'); + } + + getCombinedCodeFix(scope: ts.CombinedCodeFixScope, fixId: {}, formatOptions: ts.FormatCodeSettings, preferences: ts.UserPreferences): ts.CombinedCodeActions { + throw new Error('Method not implemented.'); + } + + applyCodeActionCommand(action: ts.InstallPackageAction, formatSettings?: ts.FormatCodeSettings): Promise; + applyCodeActionCommand(action: ts.InstallPackageAction[], formatSettings?: ts.FormatCodeSettings): Promise; + applyCodeActionCommand(action: ts.InstallPackageAction | ts.InstallPackageAction[], formatSettings?: ts.FormatCodeSettings): Promise; + applyCodeActionCommand(fileName: string, action: ts.InstallPackageAction): Promise; + applyCodeActionCommand(fileName: string, action: ts.InstallPackageAction[]): Promise; + applyCodeActionCommand(fileName: string, action: ts.InstallPackageAction | ts.InstallPackageAction[]): Promise; + applyCodeActionCommand(fileName: any, action?: any): any { + throw new Error('Method not implemented.'); + } + + getApplicableRefactors(fileName: string, positionOrRange: number | ts.TextRange, preferences: ts.UserPreferences | undefined, triggerReason?: ts.RefactorTriggerReason): ts.ApplicableRefactorInfo[] { + throw new Error('Method not implemented.'); + } + + getEditsForRefactor(fileName: string, formatOptions: ts.FormatCodeSettings, positionOrRange: number | ts.TextRange, refactorName: string, actionName: string, preferences: ts.UserPreferences | undefined): ts.RefactorEditInfo | undefined { + throw new Error('Method not implemented.'); + } + + organizeImports(scope: ts.CombinedCodeFixScope, formatOptions: ts.FormatCodeSettings, preferences: ts.UserPreferences | undefined): readonly ts.FileTextChanges[] { + throw new Error('Method not implemented.'); + } + + getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: ts.FormatCodeSettings, preferences: ts.UserPreferences | undefined): readonly ts.FileTextChanges[] { + throw new Error('Method not implemented.'); + } + + getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean): ts.EmitOutput { + throw new Error('Method not implemented.'); + } + + getProgram(): ts.Program | undefined { + return this.inner.getProgram(); + } + + toggleLineComment(fileName: string, textRange: ts.TextRange): ts.TextChange[] { + throw new Error('Method not implemented.'); + } + + toggleMultilineComment(fileName: string, textRange: ts.TextRange): ts.TextChange[] { + throw new Error('Method not implemented.'); + } + + commentSelection(fileName: string, textRange: ts.TextRange): ts.TextChange[] { + throw new Error('Method not implemented.'); + } + + uncommentSelection(fileName: string, textRange: ts.TextRange): ts.TextChange[] { + throw new Error('Method not implemented.'); + } + + dispose(): void { + throw new Error('Method not implemented.'); + } + + getNonBoundSourceFile(fileName: string): ts.SourceFile { + throw new Error('Method not implemented.'); + } + + getAutoImportProvider(): ts.Program | undefined { + throw new Error('Method not implemented.'); + } + + toLineColumnOffset?(fileName: string, position: number): ts.LineAndCharacter { + throw new Error('Method not implemented.'); + } + + // getSourceMapper(): ts.SourceMapper { + // throw new Error('Method not implemented.'); + // } + + clearSourceMapperCache(): void { + throw new Error('Method not implemented.'); + } + } + + class SyndicatePlugin implements ts.server.PluginModule { + create(createInfo: ts.server.PluginCreateInfo): ts.LanguageService { + const options = createInfo.project.getCompilerOptions(); + if (options.rootDir !== void 0) { + syndicateRootDirs.add(options.rootDir); + } + if (options.rootDirs !== void 0) { + options.rootDirs.forEach(d => syndicateRootDirs.add(d)); + } + return new SyndicateLanguageService(createInfo.languageService); + } + } + + return new SyndicatePlugin(); + +}; +export = boot;