tsserver plugin
This commit is contained in:
parent
3908f2ff26
commit
99a87be883
|
@ -1,9 +1,9 @@
|
||||||
import {
|
import {
|
||||||
isToken, isTokenType, replace, commaJoin, startPos, fixPos, joinItems,
|
isToken, isTokenType, replace, commaJoin, startPos, fixPos, joinItems,
|
||||||
anonymousTemplate, laxRead,
|
anonymousTemplate, laxRead, itemText,
|
||||||
|
|
||||||
Items, Pattern, Templates, Substitution, TokenType, Pos,
|
Items, Pattern, Templates, Substitution, TokenType,
|
||||||
SourceMap, CodeWriter, TemplateFunction, Token, itemText,
|
SourceMap, CodeWriter, TemplateFunction, Token, SpanIndex,
|
||||||
} from '../syntax/index.js';
|
} from '../syntax/index.js';
|
||||||
import {
|
import {
|
||||||
SyndicateParser, SyndicateTypedParser,
|
SyndicateParser, SyndicateTypedParser,
|
||||||
|
@ -41,16 +41,11 @@ export interface CompileOptions {
|
||||||
typescript?: boolean,
|
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 {
|
export interface CompilerOutput {
|
||||||
text: string,
|
text: string,
|
||||||
map: SourceMap,
|
map: SourceMap,
|
||||||
positionIndex: SourcePositionIndex,
|
targetToSourceMap: SpanIndex<Token>;
|
||||||
|
sourceToTargetMap: SpanIndex<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function receiverFor(s: FacetAction): Substitution {
|
function receiverFor(s: FacetAction): Substitution {
|
||||||
|
@ -83,10 +78,9 @@ export class ExpansionContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
get collectedFields(): FacetFields {
|
get collectedFields(): FacetFields {
|
||||||
if (this._collectedFields === null) {
|
// Allocates a transient array for collected fields in
|
||||||
throw new Error("Internal error: this.collectedFields === null");
|
// contexts lacking a surrounding collector - that is, for errors.
|
||||||
}
|
return this._collectedFields ?? [];
|
||||||
return this._collectedFields;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
collectField(f: Binder) {
|
collectField(f: Binder) {
|
||||||
|
@ -361,40 +355,13 @@ export function compile(options: CompileOptions): CompilerOutput {
|
||||||
const cw = new CodeWriter(inputFilename);
|
const cw = new CodeWriter(inputFilename);
|
||||||
cw.emit(tree);
|
cw.emit(tree);
|
||||||
|
|
||||||
const positionMap = cw.positionMap;
|
|
||||||
|
const text = cw.text;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
text: cw.text,
|
text,
|
||||||
map: cw.map,
|
map: cw.map,
|
||||||
positionIndex: {
|
targetToSourceMap: cw.targetToSourceMap.index(),
|
||||||
sourcePositionAt(pos: number): Pos {
|
sourceToTargetMap: cw.sourceToTargetMap.index(),
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { Pos, startPos, advancePos } from './position.js';
|
||||||
import { vlqEncode } from './vlq.js';
|
import { vlqEncode } from './vlq.js';
|
||||||
|
import { SpanInfo } from './span.js';
|
||||||
|
|
||||||
export interface SourceMap {
|
export interface SourceMap {
|
||||||
version: 3;
|
version: 3;
|
||||||
|
@ -51,7 +52,8 @@ export class CodeWriter {
|
||||||
readonly sources: Array<string> = [];
|
readonly sources: Array<string> = [];
|
||||||
readonly chunks: Array<string> = [];
|
readonly chunks: Array<string> = [];
|
||||||
readonly mappings: Array<Array<NonEmptyMapping>> = [];
|
readonly mappings: Array<Array<NonEmptyMapping>> = [];
|
||||||
readonly positionMap: Array<[number, Pos]> = [];
|
readonly targetToSourceMap = new SpanInfo<Token>();
|
||||||
|
readonly sourceToTargetMap = new SpanInfo<number>();
|
||||||
previous: Partial<SourceNameMapping> = {};
|
previous: Partial<SourceNameMapping> = {};
|
||||||
previousPos: Pos | null = null;
|
previousPos: Pos | null = null;
|
||||||
|
|
||||||
|
@ -145,24 +147,14 @@ export class CodeWriter {
|
||||||
this.mappings[this.mappings.length - 1].push(n);
|
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) {
|
chunk(p: Pos, s: string, type: TokenType) {
|
||||||
p = { ... p };
|
p = { ... p };
|
||||||
this.chunks.push(s);
|
this.chunks.push(s);
|
||||||
this.augmentPositionMap(p);
|
|
||||||
if (this.mappings.length === 0) this.finishLine();
|
if (this.mappings.length === 0) this.finishLine();
|
||||||
this.addMapping(p, type);
|
this.addMapping(p, type);
|
||||||
for (const ch of s) {
|
for (const ch of s) {
|
||||||
advancePos(p, ch);
|
advancePos(p, ch);
|
||||||
if (advancePos(this.pos, ch)) {
|
if (advancePos(this.pos, ch)) {
|
||||||
this.augmentPositionMap(p);
|
|
||||||
this.finishLine();
|
this.finishLine();
|
||||||
this.addMapping(p, type);
|
this.addMapping(p, type);
|
||||||
}
|
}
|
||||||
|
@ -179,7 +171,10 @@ export class CodeWriter {
|
||||||
} else if (i === null) {
|
} else if (i === null) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
} else {
|
} 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.chunk(i.start, i.text, i.type);
|
||||||
|
this.targetToSourceMap.add(targetStart, this.pos.pos, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ export * from './matcher.js';
|
||||||
export * from './position.js';
|
export * from './position.js';
|
||||||
export * from './reader.js';
|
export * from './reader.js';
|
||||||
export * from './scanner.js';
|
export * from './scanner.js';
|
||||||
|
export * from './span.js';
|
||||||
export * from './template.js';
|
export * from './template.js';
|
||||||
export * from './tokens.js';
|
export * from './tokens.js';
|
||||||
export * from './vlq.js';
|
export * from './vlq.js';
|
||||||
|
|
|
@ -128,11 +128,12 @@ export interface LaxReadOptions {
|
||||||
start?: Pos,
|
start?: Pos,
|
||||||
name?: string,
|
name?: string,
|
||||||
extraDelimiters?: string,
|
extraDelimiters?: string,
|
||||||
|
synthetic?: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function laxRead(source: string, options: LaxReadOptions = {}): Items {
|
export function laxRead(source: string, options: LaxReadOptions = {}): Items {
|
||||||
const start = options.start ?? startPos(options.name ?? null);
|
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);
|
if (options.extraDelimiters) scanner.addDelimiters(options.extraDelimiters);
|
||||||
const reader = new LaxReader(scanner);
|
const reader = new LaxReader(scanner);
|
||||||
return reader.readToEnd();
|
return reader.readToEnd();
|
||||||
|
|
|
@ -3,12 +3,14 @@ import { Pos, advancePos } from './position.js';
|
||||||
|
|
||||||
export abstract class Scanner implements IterableIterator<Token> {
|
export abstract class Scanner implements IterableIterator<Token> {
|
||||||
readonly pos: Pos;
|
readonly pos: Pos;
|
||||||
|
readonly synthetic: boolean | undefined;
|
||||||
charBuffer: string | null = null;
|
charBuffer: string | null = null;
|
||||||
tokenBuffer: Token | null = null;
|
tokenBuffer: Token | null = null;
|
||||||
delimiters = ' \t\n\r\'"`.,;()[]{}/';
|
delimiters = ' \t\n\r\'"`.,;()[]{}/';
|
||||||
|
|
||||||
constructor(pos: Pos) {
|
constructor(pos: Pos, synthetic?: boolean) {
|
||||||
this.pos = { ... pos };
|
this.pos = { ... pos };
|
||||||
|
this.synthetic = synthetic;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Symbol.iterator](): IterableIterator<Token> {
|
[Symbol.iterator](): IterableIterator<Token> {
|
||||||
|
@ -40,11 +42,11 @@ export abstract class Scanner implements IterableIterator<Token> {
|
||||||
}
|
}
|
||||||
|
|
||||||
makeToken(start: Pos, type: TokenType, text: string): Token {
|
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<Item> = []): GroupInProgress {
|
makeGroupInProgress(open: Token, items: Array<Item> = []): GroupInProgress {
|
||||||
return { start: open.start, open, close: null, items };
|
return { start: open.start, open, close: null, items, ... this.synthetic && { synthetic: true } };
|
||||||
}
|
}
|
||||||
|
|
||||||
mark(): Pos {
|
mark(): Pos {
|
||||||
|
@ -211,8 +213,8 @@ export class StringScanner extends Scanner {
|
||||||
readonly input: string;
|
readonly input: string;
|
||||||
index: number;
|
index: number;
|
||||||
|
|
||||||
constructor(pos: Pos, input: string) {
|
constructor(pos: Pos, input: string, synthetic?: boolean) {
|
||||||
super(pos);
|
super(pos, synthetic);
|
||||||
this.input = input;
|
this.input = input;
|
||||||
this.index = 0;
|
this.index = 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
export class SpanResult<T> {
|
||||||
|
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<T> {
|
||||||
|
readonly index: Array<[number, Array<[number, T]>]> = [];
|
||||||
|
|
||||||
|
get(pos: number): SpanResult<T> | 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<T>(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<T> {
|
||||||
|
readonly spans: Map<number, Array<[number, T]>> = 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<T> {
|
||||||
|
const i = new SpanIndex<T>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ const substPat = M.scope((o: { pos: Pos }) =>
|
||||||
export type Substitution = Items | string;
|
export type Substitution = Items | string;
|
||||||
|
|
||||||
function toItems(s: Substitution, pos: Pos): Items {
|
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;
|
export type TemplateFunction = (consts: TemplateStringsArray, ... vars: Substitution[]) => Items;
|
||||||
|
@ -42,7 +42,7 @@ export class Templates {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let i = 0;
|
let i = 0;
|
||||||
return M.replace(laxRead(source, { start, extraDelimiters: '$' }),
|
return M.replace(laxRead(source, { start, extraDelimiters: '$', synthetic: true }),
|
||||||
substPat,
|
substPat,
|
||||||
sub => toItems(vars[i++], sub.pos));
|
sub => toItems(vars[i++], sub.pos));
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,6 +12,7 @@ export enum TokenType {
|
||||||
export interface TokenBase {
|
export interface TokenBase {
|
||||||
start: Pos;
|
start: Pos;
|
||||||
end: Pos;
|
end: Pos;
|
||||||
|
synthetic?: boolean; // default: false
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Token extends TokenBase {
|
export interface Token extends TokenBase {
|
||||||
|
|
|
@ -11,7 +11,10 @@
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"module": "es6",
|
"module": "es6",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true
|
"strict": true,
|
||||||
|
"plugins": [
|
||||||
|
{ "name": "@syndicate-lang/syndicatec/lib/tsserver-plugin.js" }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"]
|
"include": ["src/**/*"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import ts from 'typescript';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
|
||||||
import { compile } from '@syndicate-lang/compiler';
|
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) {
|
function reportDiagnostic(diagnostic: ts.Diagnostic) {
|
||||||
if (diagnostic.file) {
|
if (diagnostic.file) {
|
||||||
|
@ -22,7 +22,8 @@ function reportErrorSummary(n: number) {
|
||||||
|
|
||||||
interface SyndicateInfo {
|
interface SyndicateInfo {
|
||||||
originalSource: string;
|
originalSource: string;
|
||||||
positionIndex: SourcePositionIndex;
|
targetToSourceMap: SpanIndex<Token>;
|
||||||
|
sourceToTargetMap: SpanIndex<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const syndicateInfo: Map<string, SyndicateInfo> = new Map();
|
const syndicateInfo: Map<string, SyndicateInfo> = new Map();
|
||||||
|
@ -56,19 +57,21 @@ function createProgram(rootNames: readonly string[] | undefined,
|
||||||
onError?.(`Could not read input file ${fileName}`);
|
onError?.(`Could not read input file ${fileName}`);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const { text: expandedText, positionIndex } = compile({
|
const { text: expandedText, targetToSourceMap, sourceToTargetMap } = compile({
|
||||||
source: inputText,
|
source: inputText,
|
||||||
name: fileName,
|
name: fileName,
|
||||||
typescript: true,
|
typescript: true,
|
||||||
});
|
});
|
||||||
syndicateInfo.set(fileName, {
|
syndicateInfo.set(fileName, {
|
||||||
originalSource: inputText,
|
originalSource: inputText,
|
||||||
positionIndex,
|
targetToSourceMap,
|
||||||
|
sourceToTargetMap,
|
||||||
});
|
});
|
||||||
const sf = ts.createSourceFile(fileName, expandedText, languageVersion, true);
|
const sf = ts.createSourceFile(fileName, expandedText, languageVersion, true);
|
||||||
(sf as any).version = crypto.createHash('sha256').update(expandedText).digest('hex');
|
(sf as any).version = crypto.createHash('sha256').update(expandedText).digest('hex');
|
||||||
return sf;
|
return sf;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
onError?.(e.message);
|
onError?.(e.message);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -85,36 +88,25 @@ function createProgram(rootNames: readonly string[] | undefined,
|
||||||
projectReferences);
|
projectReferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fixSourceMap(ctx: ts.TransformationContext): ts.Transformer<ts.SourceFile> {
|
export function fixSourceMap(_ctx: ts.TransformationContext): ts.Transformer<ts.SourceFile> {
|
||||||
return sf => {
|
return sf => {
|
||||||
const fileName = sf.fileName;
|
const fileName = sf.fileName;
|
||||||
const info = syndicateInfo.get(fileName);
|
const info = syndicateInfo.get(fileName);
|
||||||
if (info === void 0) throw new Error("No Syndicate info available for " + fileName);
|
if (info === void 0) throw new Error("No Syndicate info available for " + fileName);
|
||||||
const positionIndex = info.positionIndex;
|
const targetToSourceMap = info.targetToSourceMap;
|
||||||
|
|
||||||
// console.log('fixSourceMap', fileName, sf.text.length, info.originalSource.length);
|
|
||||||
|
|
||||||
const syndicateSource = ts.createSourceMapSource(fileName, info.originalSource);
|
const syndicateSource = ts.createSourceMapSource(fileName, info.originalSource);
|
||||||
const expandedSource = ts.createSourceMapSource(fileName + '.expanded', sf.text);
|
|
||||||
|
|
||||||
function adjustSourceMap(n: ts.Node) {
|
function adjustSourceMap(n: ts.Node) {
|
||||||
const ps = positionIndex.sourcePositionAt(n.pos);
|
const ps = targetToSourceMap.get(n.pos);
|
||||||
const pe = positionIndex.sourcePositionAt(n.end);
|
const pe = targetToSourceMap.get(n.end);
|
||||||
if (ps.name === fileName && pe.name === fileName) {
|
if (ps !== null && pe !== null) {
|
||||||
ts.setSourceMapRange(n, { pos: ps.pos, end: pe.pos, source: syndicateSource });
|
ts.setSourceMapRange(n, {
|
||||||
// console.group(ts.SyntaxKind[n.kind], `${n.pos}-${n.end} ==> ${ps.pos}-${pe.pos}`);
|
pos: ps.firstItem.start.pos + ps.offset,
|
||||||
} else if (ps.name === null && pe.name === null) {
|
end: pe.lastItem.start.pos + pe.offset,
|
||||||
ts.setSourceMapRange(n, { pos: ps.pos, end: pe.pos, source: expandedSource });
|
source: syndicateSource,
|
||||||
// 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');
|
|
||||||
}
|
}
|
||||||
ts.forEachChild(n, adjustSourceMap);
|
ts.forEachChild(n, adjustSourceMap);
|
||||||
// console.groupEnd();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustSourceMap(sf);
|
adjustSourceMap(sf);
|
||||||
|
@ -123,7 +115,7 @@ export function fixSourceMap(ctx: ts.TransformationContext): ts.Transformer<ts.S
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function main(argv: string[]) {
|
export function main(_argv: string[]) {
|
||||||
const sbh = ts.createSolutionBuilderHost(ts.sys,
|
const sbh = ts.createSolutionBuilderHost(ts.sys,
|
||||||
createProgram,
|
createProgram,
|
||||||
reportDiagnostic,
|
reportDiagnostic,
|
||||||
|
|
|
@ -0,0 +1,615 @@
|
||||||
|
import { compile, Syntax } from '@syndicate-lang/compiler';
|
||||||
|
import tslib from 'typescript/lib/tsserverlibrary';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
|
||||||
|
const boot: tslib.server.PluginModuleFactory = ({ typescript: ts }) => {
|
||||||
|
|
||||||
|
interface SyndicateInfo {
|
||||||
|
sourceFile: ts.SourceFile;
|
||||||
|
originalSource: string;
|
||||||
|
targetToSourceMap: Syntax.SpanIndex<Syntax.Token>;
|
||||||
|
sourceToTargetMap: Syntax.SpanIndex<number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const syndicateInfo: Map<string, SyndicateInfo> = new Map();
|
||||||
|
|
||||||
|
const syndicateRootDirs: Set<string> = 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<T extends ts.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<T extends ts.Diagnostic>(ds: T[]): T[] {
|
||||||
|
const vs: T[] = [];
|
||||||
|
ds.forEach(d => this.diagnostic(d, vs));
|
||||||
|
return vs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PositionFixup extends Fixup {
|
||||||
|
readonly target: Syntax.SpanResult<number>;
|
||||||
|
|
||||||
|
constructor(info: SyndicateInfo, target: Syntax.SpanResult<number>) {
|
||||||
|
super(info);
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
get targetStart(): number {
|
||||||
|
return this.target.firstItem + this.target.offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function withFileName<T>(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<T>(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<ts.CompletionInfo> | 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<ts.ApplyCodeActionCommandResult>;
|
||||||
|
applyCodeActionCommand(action: ts.InstallPackageAction[], formatSettings?: ts.FormatCodeSettings): Promise<ts.ApplyCodeActionCommandResult[]>;
|
||||||
|
applyCodeActionCommand(action: ts.InstallPackageAction | ts.InstallPackageAction[], formatSettings?: ts.FormatCodeSettings): Promise<ts.ApplyCodeActionCommandResult | ts.ApplyCodeActionCommandResult[]>;
|
||||||
|
applyCodeActionCommand(fileName: string, action: ts.InstallPackageAction): Promise<ts.ApplyCodeActionCommandResult>;
|
||||||
|
applyCodeActionCommand(fileName: string, action: ts.InstallPackageAction[]): Promise<ts.ApplyCodeActionCommandResult[]>;
|
||||||
|
applyCodeActionCommand(fileName: string, action: ts.InstallPackageAction | ts.InstallPackageAction[]): Promise<ts.ApplyCodeActionCommandResult | ts.ApplyCodeActionCommandResult[]>;
|
||||||
|
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;
|
Loading…
Reference in New Issue