зеркало из https://github.com/microsoft/lsif-node.git
Add a JSON based language service (#175)
* Work an a LSIF language service module * Add to @vscode namespace * Add tests * Move to language server types * Add some basic tests * Fix eslint errors * More eslint fixes * Add language service to all script
This commit is contained in:
Родитель
da622ed284
Коммит
ac447f8b20
|
@ -7,4 +7,5 @@ npm-debug.log
|
|||
*.db
|
||||
tsc-lsif/src/shared
|
||||
npm-lsif/src/shared
|
||||
util/junit.xml
|
||||
util/junit.xml
|
||||
!language-service/src/tests/dump.lsif
|
|
@ -15,7 +15,8 @@
|
|||
"./npm",
|
||||
"./sqlite",
|
||||
"./tooling",
|
||||
"./lsif"
|
||||
"./lsif",
|
||||
"./language-service"
|
||||
],
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
|
|
|
@ -20,7 +20,7 @@ if (args[0] === 'install' && process.env['npm_config_root_only'] === 'true') {
|
|||
}
|
||||
|
||||
|
||||
const folders = ['protocol', 'tsc', 'npm', 'sqlite', 'tooling', 'tsc-tests', 'lsif'];
|
||||
const folders = ['protocol', 'tsc', 'npm', 'sqlite', 'tooling', 'tsc-tests', 'lsif', 'language-service'];
|
||||
|
||||
for (const folder of folders) {
|
||||
console.log(`Running npm ${args.join(' ')} in ${folder}`);
|
||||
|
|
|
@ -67,6 +67,14 @@ try {
|
|||
mkLink(path.join('..', '..', 'protocol'), 'lsif-protocol');
|
||||
}
|
||||
|
||||
// Setup links for language-service
|
||||
{
|
||||
const sqlite = path.join(root, 'language-service', 'node_modules');
|
||||
fs.mkdirSync(sqlite, { recursive: true });
|
||||
process.chdir(sqlite);
|
||||
mkLink(path.join('..', '..', 'protocol'), 'lsif-protocol');
|
||||
}
|
||||
|
||||
// Setup symlinks for lsif commands
|
||||
{
|
||||
const lsif = path.join(root, 'lsif', 'node_modules')
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
lib
|
||||
node_modules
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../.eslintrc.base.json",
|
||||
"parserOptions": {
|
||||
"project": ["./tsconfig.json"]
|
||||
},
|
||||
"rules": {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"spec": ["lib/tests"],
|
||||
"extension": ["js"],
|
||||
"recursive": true,
|
||||
"ui": "tdd"
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
{
|
||||
"name": "@vscode/lsif-language-service",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@vscode/lsif-language-service",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lsif-protocol": "0.6.0-next.7",
|
||||
"semver": "^7.6.2",
|
||||
"vscode-languageserver-types": "^3.17.5",
|
||||
"vscode-uri": "^3.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^10.0.7",
|
||||
"@types/semver": "^7.5.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mocha": {
|
||||
"version": "10.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.7.tgz",
|
||||
"integrity": "sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
"version": "7.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
|
||||
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lsif-protocol": {
|
||||
"version": "0.6.0-next.7",
|
||||
"resolved": "https://registry.npmjs.org/lsif-protocol/-/lsif-protocol-0.6.0-next.7.tgz",
|
||||
"integrity": "sha512-P67czn1sCa4EhEFlRJWabieHB2poIuRHRO06mR5lLsTk0HqMrbM9McJaXlAJwOMylaxZ0HPpHrT9ij9BA4tzyw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vscode-languageserver-protocol": "^3.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
|
||||
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-jsonrpc": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
|
||||
"integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-languageserver-protocol": {
|
||||
"version": "3.17.5",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
|
||||
"integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vscode-jsonrpc": "8.2.0",
|
||||
"vscode-languageserver-types": "3.17.5"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-languageserver-types": {
|
||||
"version": "3.17.5",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
|
||||
"integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vscode-uri": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
|
||||
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "@vscode/lsif-language-service",
|
||||
"description": "LSIF based language services",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"author": "Microsoft Corporation",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Microsoft/lsif-node.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Microsoft/lsif-node/issues"
|
||||
},
|
||||
"main": "lib/main.js",
|
||||
"typings": "lib/main.d.ts",
|
||||
"dependencies": {
|
||||
"vscode-uri": "^3.0.8",
|
||||
"semver": "^7.6.2",
|
||||
"lsif-protocol": "0.6.0-next.7",
|
||||
"vscode-languageserver-types": "^3.17.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/semver": "^7.5.7",
|
||||
"@types/mocha": "^10.0.7"
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "tsc -b ./tsconfig.json",
|
||||
"compile:publish": "tsc -p./tsconfig.publish.json",
|
||||
"watch": "tsc -b ./tsconfig.watch.json -w",
|
||||
"clean": "rimraf lib",
|
||||
"clean:all": "tsc -b ./tsconfig.json --clean",
|
||||
"lint": "eslint ./src/*.ts",
|
||||
"test": "mocha",
|
||||
"prepublishOnly": "git clean -xfd . && npm install && npm run clean && npm run compile:publish && npm run lint",
|
||||
"postpublish": "node ../build/bin/post-publish.js",
|
||||
"postinstall": ""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
import { URI } from 'vscode-uri';
|
||||
import * as lsp from 'vscode-languageserver-types';
|
||||
import { Range, Id } from 'lsif-protocol';
|
||||
|
||||
import { FileType, FileSystem, DocumentInfo, FileStat } from './files';
|
||||
|
||||
export interface UriTransformer {
|
||||
toDatabase(uri: string): string;
|
||||
fromDatabase(uri: string): string;
|
||||
}
|
||||
|
||||
export const noopTransformer: UriTransformer = {
|
||||
toDatabase: uri => uri,
|
||||
fromDatabase: uri => uri
|
||||
};
|
||||
|
||||
export abstract class Database {
|
||||
|
||||
private fileSystem!: FileSystem;
|
||||
private uriTransformer!: UriTransformer;
|
||||
|
||||
protected constructor() {
|
||||
}
|
||||
|
||||
protected initialize(transformerFactory?: (workspaceRoot: string) => UriTransformer): void {
|
||||
const workspaceRoot = this.getWorkspaceRoot().toString(true);
|
||||
this.uriTransformer = transformerFactory ? transformerFactory(workspaceRoot) : noopTransformer;
|
||||
this.fileSystem = new FileSystem(workspaceRoot, this.getDocumentInfos());
|
||||
}
|
||||
|
||||
public abstract load(file: string, transformerFactory: (workspaceRoot: string) => UriTransformer): Promise<void>;
|
||||
|
||||
public abstract close(): void;
|
||||
|
||||
public abstract getWorkspaceRoot(): URI;
|
||||
|
||||
protected abstract getDocumentInfos(): DocumentInfo[];
|
||||
|
||||
public stat(uri: string): FileStat | null {
|
||||
const transformed = this.uriTransformer.toDatabase(uri);
|
||||
const result = this.fileSystem.stat(transformed);
|
||||
if (result !== null) {
|
||||
return result;
|
||||
}
|
||||
const id = this.findFile(transformed);
|
||||
if (id === undefined) {
|
||||
return null;
|
||||
}
|
||||
return FileStat.createFile();
|
||||
}
|
||||
|
||||
public readDirectory(uri: string): [string, FileType][] {
|
||||
return this.fileSystem.readDirectory(this.uriTransformer.toDatabase(uri));
|
||||
}
|
||||
|
||||
public readFileContent(uri: string): string | null {
|
||||
const transformed = this.uriTransformer.toDatabase(uri);
|
||||
let info = this.fileSystem.getFileInfo(transformed);
|
||||
if (info === undefined) {
|
||||
info = this.findFile(transformed);
|
||||
}
|
||||
if (info === undefined) {
|
||||
return null;
|
||||
}
|
||||
const result = this.fileContent(info);
|
||||
if (result === undefined) {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract findFile(uri: string):{ id: Id; hash: string | undefined } | undefined;
|
||||
|
||||
protected abstract fileContent( info: { id: Id; hash: string | undefined } ) : string | undefined;
|
||||
|
||||
public abstract foldingRanges(uri: string): lsp.FoldingRange[] | undefined;
|
||||
|
||||
public abstract documentSymbols(uri: string): lsp.DocumentSymbol[] | undefined;
|
||||
|
||||
public abstract hover(uri: string, position: lsp.Position): lsp.Hover | undefined;
|
||||
|
||||
public abstract declarations(uri: string, position: lsp.Position): lsp.Location | lsp.Location[] | undefined;
|
||||
|
||||
public abstract definitions(uri: string, position: lsp.Position): lsp.Location | lsp.Location[] | undefined;
|
||||
|
||||
public abstract references(uri: string, position: lsp.Position, context: lsp.ReferenceContext): lsp.Location[] | undefined;
|
||||
|
||||
protected asDocumentSymbol(range: Range): lsp.DocumentSymbol | undefined {
|
||||
const tag = range.tag;
|
||||
if (tag === undefined || !(tag.type === 'declaration' || tag.type === 'definition')) {
|
||||
return undefined;
|
||||
}
|
||||
return lsp.DocumentSymbol.create(
|
||||
tag.text, tag.detail || '', tag.kind,
|
||||
tag.fullRange, this.asRange(range)
|
||||
);
|
||||
}
|
||||
|
||||
protected asRange(value: Range): lsp.Range {
|
||||
return {
|
||||
start: {
|
||||
line: value.start.line,
|
||||
character: value.start.character
|
||||
},
|
||||
end: {
|
||||
line: value.end.line,
|
||||
character: value.end.character
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected toDatabase(uri: string): string {
|
||||
return this.uriTransformer.toDatabase(uri);
|
||||
}
|
||||
|
||||
protected fromDatabase(uri: string): string {
|
||||
return this.uriTransformer.fromDatabase(uri);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
import * as path from 'path';
|
||||
|
||||
import { Id } from 'lsif-protocol';
|
||||
|
||||
const ctime = Date.now();
|
||||
const mtime = Date.now();
|
||||
|
||||
export namespace FileType {
|
||||
export const Unknown: 0 = 0;
|
||||
export const File: 1 = 1;
|
||||
export const Directory: 2 = 2;
|
||||
export const SymbolicLink: 64 = 64;
|
||||
}
|
||||
|
||||
export type FileType = 0 | 1 | 2 | 64;
|
||||
|
||||
export interface FileStat {
|
||||
type: FileType;
|
||||
ctime: number;
|
||||
mtime: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export namespace FileStat {
|
||||
export function createFile(): FileStat {
|
||||
return { type: FileType.File, ctime: ctime, mtime: mtime, size: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
export interface DocumentInfo {
|
||||
id: Id;
|
||||
uri: string;
|
||||
hash: string;
|
||||
}
|
||||
|
||||
interface File extends FileStat {
|
||||
type: 1;
|
||||
name: string;
|
||||
id: Id;
|
||||
hash: string;
|
||||
}
|
||||
|
||||
namespace File {
|
||||
export function create(name: string, id: Id, hash: string): File {
|
||||
return { type: FileType.File, ctime: ctime, mtime: mtime, size: 0, name, id, hash };
|
||||
}
|
||||
}
|
||||
|
||||
interface Directory extends FileStat {
|
||||
type: 2;
|
||||
name: string;
|
||||
children: Map<string, Entry>;
|
||||
}
|
||||
|
||||
namespace Directory {
|
||||
export function create(name: string): Directory {
|
||||
return { type: FileType.Directory, ctime: Date.now(), mtime: Date.now(), size: 0, name, children: new Map() };
|
||||
}
|
||||
}
|
||||
|
||||
export type Entry = File | Directory;
|
||||
|
||||
export class FileSystem {
|
||||
|
||||
private workspaceRoot: string;
|
||||
private workspaceRootWithSlash: string;
|
||||
private filesOutsideWorkspaceRoot: Map<string, { id: Id; hash: string | undefined }>;
|
||||
private root: Directory;
|
||||
|
||||
constructor(workspaceRoot: string, documents: DocumentInfo[]) {
|
||||
if (workspaceRoot.charAt(workspaceRoot.length - 1) === '/') {
|
||||
this.workspaceRoot = workspaceRoot.substr(0, workspaceRoot.length - 1);
|
||||
this.workspaceRootWithSlash = workspaceRoot;
|
||||
} else {
|
||||
this.workspaceRoot = workspaceRoot;
|
||||
this.workspaceRootWithSlash = workspaceRoot + '/';
|
||||
}
|
||||
this.root = Directory.create('');
|
||||
this.filesOutsideWorkspaceRoot = new Map();
|
||||
for (const info of documents) {
|
||||
// Do not show file outside the workspaceRoot.
|
||||
if (!info.uri.startsWith(this.workspaceRootWithSlash)) {
|
||||
this.filesOutsideWorkspaceRoot.set(info.uri, info);
|
||||
continue;
|
||||
}
|
||||
const p = info.uri.substring(workspaceRoot.length);
|
||||
const dirname = path.posix.dirname(p);
|
||||
const basename = path.posix.basename(p);
|
||||
const entry = this.lookup(dirname, true);
|
||||
if (entry && entry.type === FileType.Directory) {
|
||||
entry.children.set(basename, File.create(basename, info.id, info.hash));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public stat(uri: string): FileStat | null {
|
||||
if (this.filesOutsideWorkspaceRoot.has(uri)) {
|
||||
return { type: FileType.File, ctime, mtime, size: 0 };
|
||||
}
|
||||
const isRoot = this.workspaceRoot === uri;
|
||||
if (!uri.startsWith(this.workspaceRootWithSlash) && !isRoot) {
|
||||
return null;
|
||||
}
|
||||
const p = isRoot ? '' : uri.substring(this.workspaceRootWithSlash.length);
|
||||
const entry = this.lookup(p, false);
|
||||
return entry ? entry : null;
|
||||
}
|
||||
|
||||
public readDirectory(uri: string): [string, FileType][] {
|
||||
const isRoot = this.workspaceRoot === uri;
|
||||
if (!uri.startsWith(this.workspaceRootWithSlash) && !isRoot) {
|
||||
return [];
|
||||
}
|
||||
const p = isRoot ? '' : uri.substring(this.workspaceRootWithSlash.length);
|
||||
const entry = this.lookup(p, false);
|
||||
if (entry === undefined || entry.type !== FileType.Directory) {
|
||||
return [];
|
||||
}
|
||||
const result: [string, FileType][] = [];
|
||||
for (const child of entry.children.values()) {
|
||||
result.push([child.name, child.type]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getFileInfo(uri: string): { id: Id; hash: string | undefined } | undefined {
|
||||
const result = this.filesOutsideWorkspaceRoot.get(uri);
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
const isRoot = this.workspaceRoot === uri;
|
||||
if (!uri.startsWith(this.workspaceRootWithSlash) && !isRoot) {
|
||||
return undefined;
|
||||
}
|
||||
const entry = this.lookup(isRoot ? '' : uri.substring(this.workspaceRootWithSlash.length));
|
||||
return entry && entry.type === FileType.File ? entry : undefined;
|
||||
}
|
||||
|
||||
private lookup(uri: string, create: boolean = false): Entry | undefined {
|
||||
const parts = uri.split('/');
|
||||
let entry: Entry = this.root;
|
||||
for (const part of parts) {
|
||||
if (!part || part === '.') {
|
||||
continue;
|
||||
}
|
||||
let child: Entry | undefined;
|
||||
if (entry.type === FileType.Directory) {
|
||||
child = entry.children.get(part);
|
||||
if (child === undefined && create) {
|
||||
child = Directory.create(part);
|
||||
entry.children.set(part, child);
|
||||
}
|
||||
}
|
||||
if (!child) {
|
||||
return undefined;
|
||||
}
|
||||
entry = child;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,732 @@
|
|||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
import * as fs from 'fs';
|
||||
import * as crypto from 'crypto';
|
||||
import * as readline from 'readline';
|
||||
|
||||
import { URI } from 'vscode-uri';
|
||||
import * as SemVer from 'semver';
|
||||
|
||||
import * as lsp from 'vscode-languageserver-types';
|
||||
import {
|
||||
Id, Vertex, Project, Document, Range, DiagnosticResult, DocumentSymbolResult, FoldingRangeResult, DocumentLinkResult, DefinitionResult,
|
||||
TypeDefinitionResult, HoverResult, ReferenceResult, ImplementationResult, Edge, RangeBasedDocumentSymbol, DeclarationResult,
|
||||
ElementTypes, VertexLabels, EdgeLabels, ItemEdgeProperties, EventScope, EventKind, ProjectEvent, Moniker as PMoniker, MonikerKind
|
||||
} from 'lsif-protocol';
|
||||
|
||||
import { DocumentInfo } from './files';
|
||||
import { Database, UriTransformer } from './database';
|
||||
|
||||
interface Moniker extends PMoniker {
|
||||
key: string;
|
||||
}
|
||||
|
||||
interface Vertices {
|
||||
all: Map<Id, Vertex>;
|
||||
projects: Map<Id, Project>;
|
||||
documents: Map<Id, Document>;
|
||||
ranges: Map<Id, Range>;
|
||||
}
|
||||
|
||||
type ItemTarget =
|
||||
Range |
|
||||
{ type: ItemEdgeProperties.declarations; range: Range } |
|
||||
{ type: ItemEdgeProperties.definitions; range: Range } |
|
||||
{ type: ItemEdgeProperties.references; range: Range } |
|
||||
{ type: ItemEdgeProperties.referenceResults; result: ReferenceResult } |
|
||||
{ type: ItemEdgeProperties.referenceLinks; result: Moniker };
|
||||
|
||||
interface Out {
|
||||
contains: Map<Id, Document[] | Range[]>;
|
||||
item: Map<Id, ItemTarget[]>;
|
||||
next: Map<Id, Vertex>;
|
||||
moniker: Map<Id, Moniker>;
|
||||
documentSymbol: Map<Id, DocumentSymbolResult>;
|
||||
foldingRange: Map<Id, FoldingRangeResult>;
|
||||
documentLink: Map<Id, DocumentLinkResult>;
|
||||
diagnostic: Map<Id, DiagnosticResult>;
|
||||
declaration: Map<Id, DeclarationResult>;
|
||||
definition: Map<Id, DefinitionResult>;
|
||||
typeDefinition: Map<Id, TypeDefinitionResult>;
|
||||
hover: Map<Id, HoverResult>;
|
||||
references: Map<Id, ReferenceResult>;
|
||||
implementation: Map<Id, ImplementationResult>;
|
||||
}
|
||||
|
||||
interface In {
|
||||
contains: Map<Id, Project | Document>;
|
||||
moniker: Map<Id, Vertex[]>;
|
||||
}
|
||||
|
||||
interface Indices {
|
||||
monikers: Map<string, Moniker[]>;
|
||||
contents: Map<string, string>;
|
||||
documents: Map<string, { hash: string; documents: Document[] }>;
|
||||
}
|
||||
|
||||
interface ResultPath<T> {
|
||||
path: { vertex: Id; moniker: Moniker | undefined }[];
|
||||
result: { value: T; moniker: Moniker | undefined } | undefined;
|
||||
}
|
||||
|
||||
namespace Locations {
|
||||
export function makeKey(location: lsp.Location): string {
|
||||
const range = location.range;
|
||||
return crypto.createHash('md5').update(JSON.stringify({ d: location.uri, sl: range.start.line, sc: range.start.character, el: range.end.line, ec: range.end.character }, undefined, 0)).digest('base64');
|
||||
}
|
||||
}
|
||||
|
||||
export class JsonStore extends Database {
|
||||
|
||||
private version: string | undefined;
|
||||
private workspaceRoot!: URI;
|
||||
protected activeProject: Id | undefined;
|
||||
|
||||
private vertices: Vertices;
|
||||
private indices: Indices;
|
||||
private out: Out;
|
||||
private in: In;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.vertices = {
|
||||
all: new Map(),
|
||||
projects: new Map(),
|
||||
documents: new Map(),
|
||||
ranges: new Map()
|
||||
};
|
||||
|
||||
this.indices = {
|
||||
contents: new Map(),
|
||||
documents: new Map(),
|
||||
monikers: new Map(),
|
||||
};
|
||||
|
||||
this.out = {
|
||||
contains: new Map(),
|
||||
item: new Map(),
|
||||
next: new Map(),
|
||||
moniker: new Map(),
|
||||
documentSymbol: new Map(),
|
||||
foldingRange: new Map(),
|
||||
documentLink: new Map(),
|
||||
diagnostic: new Map(),
|
||||
declaration: new Map(),
|
||||
definition: new Map(),
|
||||
typeDefinition: new Map(),
|
||||
hover: new Map(),
|
||||
references: new Map(),
|
||||
implementation: new Map()
|
||||
};
|
||||
|
||||
this.in = {
|
||||
contains: new Map(),
|
||||
moniker: new Map()
|
||||
};
|
||||
}
|
||||
|
||||
public load(file: string, transformerFactory?: (workspaceRoot: string) => UriTransformer): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const input: fs.ReadStream = fs.createReadStream(file, { encoding: 'utf8'});
|
||||
input.on('error', reject);
|
||||
const rd = readline.createInterface(input);
|
||||
rd.on('line', (line: string) => {
|
||||
if (!line || line.length === 0) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const element: Edge | Vertex = JSON.parse(line);
|
||||
switch (element.type) {
|
||||
case ElementTypes.vertex:
|
||||
this.processVertex(element);
|
||||
break;
|
||||
case ElementTypes.edge:
|
||||
this.processEdge(element);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
input.destroy();
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
rd.on('close', () => {
|
||||
if (this.workspaceRoot === undefined) {
|
||||
reject(new Error('No project root provided.'));
|
||||
return;
|
||||
}
|
||||
if (this.version === undefined) {
|
||||
reject(new Error('No version found.'));
|
||||
return;
|
||||
} else {
|
||||
const semVer = SemVer.parse(this.version);
|
||||
if (!semVer) {
|
||||
reject(new Error(`No valid semantic version string. The version is: ${this.version}`));
|
||||
return;
|
||||
}
|
||||
const range: SemVer.Range = new SemVer.Range('>0.5.99 <=0.6.0-next.7');
|
||||
range.includePrerelease = true;
|
||||
if (!SemVer.satisfies(semVer, range)) {
|
||||
reject(new Error(`Requires version range >0.5.99 <=0.6.0-next.7 but received: ${this.version}`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}).then(() => {
|
||||
this.initialize(transformerFactory);
|
||||
});
|
||||
}
|
||||
|
||||
public getWorkspaceRoot(): URI {
|
||||
return this.workspaceRoot;
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
}
|
||||
|
||||
private processVertex(vertex: Vertex): void {
|
||||
this.vertices.all.set(vertex.id, vertex);
|
||||
switch(vertex.label) {
|
||||
case VertexLabels.metaData:
|
||||
this.version = vertex.version;
|
||||
break;
|
||||
case VertexLabels.source:
|
||||
this.workspaceRoot = URI.parse(vertex.workspaceRoot);
|
||||
break;
|
||||
case VertexLabels.project:
|
||||
this.vertices.projects.set(vertex.id, vertex);
|
||||
break;
|
||||
case VertexLabels.event:
|
||||
if (vertex.kind === EventKind.begin) {
|
||||
switch (vertex.scope) {
|
||||
case EventScope.project:
|
||||
this.activeProject = (vertex as ProjectEvent).data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case VertexLabels.document:
|
||||
this.doProcessDocument(vertex);
|
||||
break;
|
||||
case VertexLabels.moniker:
|
||||
if (vertex.kind !== MonikerKind.local) {
|
||||
const key = crypto.createHash('md5').update(JSON.stringify({ s: vertex.scheme, i: vertex.identifier }, undefined, 0)).digest('base64');
|
||||
(vertex as Moniker).key = key;
|
||||
let values = this.indices.monikers.get(key);
|
||||
if (values === undefined) {
|
||||
values = [];
|
||||
this.indices.monikers.set(key, values);
|
||||
}
|
||||
values.push(vertex as Moniker);
|
||||
}
|
||||
break;
|
||||
case VertexLabels.range:
|
||||
this.vertices.ranges.set(vertex.id, vertex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private doProcessDocument(document: Document): void {
|
||||
const contents = document.contents !== undefined ? document.contents : 'No content provided.';
|
||||
this.vertices.documents.set(document.id, document);
|
||||
const hash = crypto.createHash('md5').update(contents).digest('base64');
|
||||
this.indices.contents.set(hash, contents);
|
||||
|
||||
let value = this.indices.documents.get(document.uri);
|
||||
if (value === undefined) {
|
||||
value = { hash, documents: [] };
|
||||
this.indices.documents.set(document.uri, value);
|
||||
}
|
||||
if (hash !== value.hash) {
|
||||
console.error(`Document ${document.uri} has different content.`);
|
||||
}
|
||||
value.documents.push(document);
|
||||
}
|
||||
|
||||
private processEdge(edge: Edge): void {
|
||||
let property: ItemEdgeProperties | undefined;
|
||||
if (edge.label === 'item') {
|
||||
property = edge.property;
|
||||
}
|
||||
if (Edge.is11(edge)) {
|
||||
this.doProcessEdge(edge.label, edge.outV, edge.inV, property);
|
||||
} else if (Edge.is1N(edge)) {
|
||||
for (const inV of edge.inVs) {
|
||||
this.doProcessEdge(edge.label, edge.outV, inV, property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private doProcessEdge(label: EdgeLabels, outV: Id, inV: Id, property?: ItemEdgeProperties): void {
|
||||
const from: Vertex | undefined = this.vertices.all.get(outV);
|
||||
const to: Vertex | undefined = this.vertices.all.get(inV);
|
||||
if (from === undefined) {
|
||||
throw new Error(`No vertex found for Id ${outV}`);
|
||||
}
|
||||
if (to === undefined) {
|
||||
throw new Error(`No vertex found for Id ${inV}`);
|
||||
}
|
||||
let values: any[] | undefined;
|
||||
let itemTarget: ItemTarget | undefined;
|
||||
switch (label) {
|
||||
case EdgeLabels.contains:
|
||||
values = this.out.contains.get(from.id);
|
||||
if (values === undefined) {
|
||||
values = [ to as any ];
|
||||
this.out.contains.set(from.id, values);
|
||||
} else {
|
||||
values.push(to);
|
||||
}
|
||||
this.in.contains.set(to.id, from as any);
|
||||
break;
|
||||
case EdgeLabels.item:
|
||||
values = this.out.item.get(from.id);
|
||||
if (property !== undefined) {
|
||||
switch (property) {
|
||||
case ItemEdgeProperties.references:
|
||||
itemTarget = { type: property, range: to as Range };
|
||||
break;
|
||||
case ItemEdgeProperties.declarations:
|
||||
itemTarget = { type: property, range: to as Range };
|
||||
break;
|
||||
case ItemEdgeProperties.definitions:
|
||||
itemTarget = { type: property, range: to as Range };
|
||||
break;
|
||||
case ItemEdgeProperties.referenceResults:
|
||||
itemTarget = { type: property, result: to as ReferenceResult };
|
||||
break;
|
||||
case ItemEdgeProperties.referenceLinks:
|
||||
itemTarget = { type: property, result: to as Moniker };
|
||||
}
|
||||
} else {
|
||||
itemTarget = to as Range;
|
||||
}
|
||||
if (itemTarget !== undefined) {
|
||||
if (values === undefined) {
|
||||
values = [ itemTarget ];
|
||||
this.out.item.set(from.id, values);
|
||||
} else {
|
||||
values.push(itemTarget);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EdgeLabels.next:
|
||||
this.out.next.set(from.id, to);
|
||||
break;
|
||||
case EdgeLabels.moniker:
|
||||
this.out.moniker.set(from.id, to as Moniker);
|
||||
values = this.in.moniker.get(to.id);
|
||||
if (values === undefined) {
|
||||
values = [];
|
||||
this.in.moniker.set(to.id, values);
|
||||
}
|
||||
values.push(from);
|
||||
break;
|
||||
case EdgeLabels.textDocument_documentSymbol:
|
||||
this.out.documentSymbol.set(from.id, to as DocumentSymbolResult);
|
||||
break;
|
||||
case EdgeLabels.textDocument_foldingRange:
|
||||
this.out.foldingRange.set(from.id, to as FoldingRangeResult);
|
||||
break;
|
||||
case EdgeLabels.textDocument_documentLink:
|
||||
this.out.documentLink.set(from.id, to as DocumentLinkResult);
|
||||
break;
|
||||
case EdgeLabels.textDocument_diagnostic:
|
||||
this.out.diagnostic.set(from.id, to as DiagnosticResult);
|
||||
break;
|
||||
case EdgeLabels.textDocument_definition:
|
||||
this.out.definition.set(from.id, to as DefinitionResult);
|
||||
break;
|
||||
case EdgeLabels.textDocument_typeDefinition:
|
||||
this.out.typeDefinition.set(from.id, to as TypeDefinitionResult);
|
||||
break;
|
||||
case EdgeLabels.textDocument_hover:
|
||||
this.out.hover.set(from.id, to as HoverResult);
|
||||
break;
|
||||
case EdgeLabels.textDocument_references:
|
||||
this.out.references.set(from.id, to as ReferenceResult);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public getDocumentInfos(): DocumentInfo[] {
|
||||
const result: DocumentInfo[] = [];
|
||||
this.indices.documents.forEach((value, key) => {
|
||||
// We take the id of the first document.
|
||||
result.push({ uri: key, id: value.documents[0].id, hash: value.hash });
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
protected findFile(uri: string): { id: Id; hash: string } | undefined {
|
||||
const result = this.indices.documents.get(uri);
|
||||
if (result === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return { id: result.documents[0].id, hash: result.hash };
|
||||
}
|
||||
|
||||
protected fileContent(info: { id: Id; hash: string }): string | undefined {
|
||||
return this.indices.contents.get(info.hash);
|
||||
}
|
||||
|
||||
public foldingRanges(uri: string): lsp.FoldingRange[] | undefined {
|
||||
const value = this.indices.documents.get(this.toDatabase(uri));
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
// Take the id of the first document with that content. We assume that
|
||||
// all documents with the same content have the same folding ranges.
|
||||
const id = value.documents[0].id;
|
||||
const foldingRangeResult = this.out.foldingRange.get(id);
|
||||
if (foldingRangeResult === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const result: lsp.FoldingRange[] = [];
|
||||
for (const item of foldingRangeResult.result) {
|
||||
result.push(Object.assign(Object.create(null), item));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public documentSymbols(uri: string): lsp.DocumentSymbol[] | undefined {
|
||||
const value = this.indices.documents.get(this.toDatabase(uri));
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
// Take the id of the first document with that content. We assume that
|
||||
// all documents with the same content have the same document symbols.
|
||||
const id = value.documents[0].id;
|
||||
const documentSymbolResult = this.out.documentSymbol.get(id);
|
||||
if (documentSymbolResult === undefined || documentSymbolResult.result.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const first = documentSymbolResult.result[0];
|
||||
const result: lsp.DocumentSymbol[] = [];
|
||||
if (lsp.DocumentSymbol.is(first)) {
|
||||
for (const item of documentSymbolResult.result) {
|
||||
result.push(Object.assign(Object.create(null), item));
|
||||
}
|
||||
} else {
|
||||
for (const item of (documentSymbolResult.result as RangeBasedDocumentSymbol[])) {
|
||||
const converted = this.toDocumentSymbol(item);
|
||||
if (converted !== undefined) {
|
||||
result.push(converted);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private toDocumentSymbol(value: RangeBasedDocumentSymbol): lsp.DocumentSymbol | undefined {
|
||||
const range = this.vertices.ranges.get(value.id)!;
|
||||
const tag = range.tag;
|
||||
if (tag === undefined || !(tag.type === 'declaration' || tag.type === 'definition')) {
|
||||
return undefined;
|
||||
}
|
||||
const result: lsp.DocumentSymbol = lsp.DocumentSymbol.create(
|
||||
tag.text, tag.detail || '', tag.kind,
|
||||
tag.fullRange, this.asRange(range)
|
||||
);
|
||||
if (value.children && value.children.length > 0) {
|
||||
result.children = [];
|
||||
for (const child of value.children) {
|
||||
const converted = this.toDocumentSymbol(child);
|
||||
if (converted !== undefined) {
|
||||
result.children.push(converted);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public hover(uri: string, position: lsp.Position): lsp.Hover | undefined {
|
||||
const ranges = this.findRangesFromPosition(this.toDatabase(uri), position);
|
||||
if (ranges === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// We assume that for the same document URI the same position results in the same
|
||||
// hover. So we take the first range.
|
||||
const range = ranges[0];
|
||||
const hoverResult = this.getResultPath(range.id, this.out.hover).result?.value;
|
||||
if (hoverResult === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const hoverRange = hoverResult.result.range !== undefined ? hoverResult.result.range : range;
|
||||
return {
|
||||
contents: hoverResult.result.contents,
|
||||
range: hoverRange
|
||||
};
|
||||
}
|
||||
|
||||
public declarations(uri: string, position: lsp.Position): lsp.Location | lsp.Location[] | undefined {
|
||||
return this.findTargets(uri, position, this.out.declaration);
|
||||
}
|
||||
|
||||
public definitions(uri: string, position: lsp.Position): lsp.Location | lsp.Location[] | undefined {
|
||||
return this.findTargets(uri, position, this.out.definition);
|
||||
}
|
||||
|
||||
private findTargets<T extends (DefinitionResult | DeclarationResult)>(uri: string, position: lsp.Position, edges: Map<Id, T>): lsp.Location | lsp.Location[] | undefined {
|
||||
const ranges = this.findRangesFromPosition(this.toDatabase(uri), position);
|
||||
if (ranges === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const resolveTargets = (result: lsp.Location[], dedupLocations: Set<string>, targetResult: T): void => {
|
||||
const ranges = this.item(targetResult);
|
||||
if (ranges === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
for (const element of ranges) {
|
||||
this.addLocation(result, element, dedupLocations);
|
||||
}
|
||||
};
|
||||
|
||||
const _findTargets = (result: lsp.Location[], dedupLocations: Set<string>, dedupMonikers: Set<string>, range: Range): void => {
|
||||
const resultPath = this.getResultPath(range.id, edges);
|
||||
if (resultPath.result === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const mostSpecificMoniker = this.getMostSpecificMoniker(resultPath);
|
||||
const monikers: Moniker[] = mostSpecificMoniker !== undefined ? [mostSpecificMoniker] : [];
|
||||
|
||||
resolveTargets(result, dedupLocations, resultPath.result.value);
|
||||
for (const moniker of monikers) {
|
||||
if (dedupMonikers.has(moniker.key)) {
|
||||
continue;
|
||||
}
|
||||
dedupMonikers.add(moniker.key);
|
||||
const matchingMonikers = this.indices.monikers.get(moniker.key);
|
||||
if (matchingMonikers !== undefined) {
|
||||
for (const matchingMoniker of matchingMonikers) {
|
||||
const vertices = this.findVerticesForMoniker(matchingMoniker);
|
||||
if (vertices !== undefined) {
|
||||
for (const vertex of vertices) {
|
||||
const resultPath = this.getResultPath(vertex.id, edges);
|
||||
if (resultPath.result === undefined) {
|
||||
continue;
|
||||
}
|
||||
resolveTargets(result, dedupLocations, resultPath.result.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const result: lsp.Location[] = [];
|
||||
const dedupLocations: Set<string> = new Set();
|
||||
const dedupMonikers: Set<string> = new Set();
|
||||
for (const range of ranges) {
|
||||
_findTargets(result, dedupLocations, dedupMonikers, range);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public references(uri: string, position: lsp.Position, context: lsp.ReferenceContext): lsp.Location[] | undefined {
|
||||
const ranges = this.findRangesFromPosition(this.toDatabase(uri), position);
|
||||
if (ranges === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const findReferences = (result: lsp.Location[], dedupLocations: Set<string>, dedupMonikers: Set<string>, range: Range): void => {
|
||||
const resultPath = this.getResultPath(range.id, this.out.references);
|
||||
if (resultPath.result === undefined) {
|
||||
return;
|
||||
}
|
||||
const mostSpecificMoniker = this.getMostSpecificMoniker(resultPath);
|
||||
const monikers: Moniker[] = mostSpecificMoniker !== undefined ? [mostSpecificMoniker] : [];
|
||||
this.resolveReferenceResult(result, dedupLocations, monikers, resultPath.result.value, context);
|
||||
for (const moniker of monikers) {
|
||||
if (dedupMonikers.has(moniker.key)) {
|
||||
continue;
|
||||
}
|
||||
dedupMonikers.add(moniker.key);
|
||||
const matchingMonikers = this.indices.monikers.get(moniker.key);
|
||||
if (matchingMonikers !== undefined) {
|
||||
for (const matchingMoniker of matchingMonikers) {
|
||||
if (moniker.id === matchingMoniker.id) {
|
||||
continue;
|
||||
}
|
||||
const vertices = this.findVerticesForMoniker(matchingMoniker);
|
||||
if (vertices !== undefined) {
|
||||
for (const vertex of vertices) {
|
||||
const resultPath = this.getResultPath(vertex.id, this.out.references);
|
||||
if (resultPath.result === undefined) {
|
||||
continue;
|
||||
}
|
||||
this.resolveReferenceResult(result, dedupLocations, monikers, resultPath.result.value, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const result: lsp.Location[] = [];
|
||||
const dedupLocations: Set<string> = new Set();
|
||||
const dedupMonikers: Set<string> = new Set();
|
||||
for (const range of ranges) {
|
||||
findReferences(result, dedupLocations, dedupMonikers, range);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private getResultPath<T>(start: Id, edges: Map<Id, T>): ResultPath<T> {
|
||||
let currentId = start;
|
||||
const result: ResultPath<T> = { path: [], result: undefined };
|
||||
do {
|
||||
const value: T | undefined = edges.get(currentId);
|
||||
const moniker: Moniker | undefined = this.out.moniker.get(currentId);
|
||||
if (value !== undefined) {
|
||||
result.result = { value, moniker };
|
||||
return result;
|
||||
}
|
||||
result.path.push({ vertex: currentId, moniker });
|
||||
const next = this.out.next.get(currentId);
|
||||
if (next === undefined) {
|
||||
return result;
|
||||
}
|
||||
currentId = next.id;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
private getMostSpecificMoniker<T>(result: ResultPath<T>): Moniker | undefined {
|
||||
if (result.result?.moniker !== undefined) {
|
||||
return result.result.moniker;
|
||||
}
|
||||
for (let i = result.path.length - 1; i >= 0; i--) {
|
||||
if (result.path[i].moniker !== undefined) {
|
||||
return result.path[i].moniker;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private findVerticesForMoniker(moniker: Moniker): Vertex[] | undefined {
|
||||
return this.in.moniker.get(moniker.id);
|
||||
}
|
||||
|
||||
private resolveReferenceResult(locations: lsp.Location[], dedupLocations: Set<string>, monikers: Moniker[], referenceResult: ReferenceResult, context: lsp.ReferenceContext): void {
|
||||
const targets = this.item(referenceResult);
|
||||
if (targets === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
for (const target of targets) {
|
||||
if (target.type === ItemEdgeProperties.declarations && context.includeDeclaration) {
|
||||
this.addLocation(locations, target.range, dedupLocations);
|
||||
} else if (target.type === ItemEdgeProperties.definitions && context.includeDeclaration) {
|
||||
this.addLocation(locations, target.range, dedupLocations);
|
||||
} else if (target.type === ItemEdgeProperties.references) {
|
||||
this.addLocation(locations, target.range, dedupLocations);
|
||||
} else if (target.type === ItemEdgeProperties.referenceResults) {
|
||||
this.resolveReferenceResult(locations, dedupLocations, monikers, target.result, context);
|
||||
} else if (target.type === ItemEdgeProperties.referenceLinks) {
|
||||
monikers.push(target.result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private item(value: DefinitionResult | DeclarationResult): Range[];
|
||||
private item(value: ReferenceResult): ItemTarget[];
|
||||
private item(value: DeclarationResult | DefinitionResult | ReferenceResult): Range[] | ItemTarget[] | undefined {
|
||||
if (value.label === 'declarationResult') {
|
||||
return this.out.item.get(value.id) as Range[];
|
||||
} else if (value.label === 'definitionResult') {
|
||||
return this.out.item.get(value.id) as Range[];
|
||||
} else if (value.label === 'referenceResult') {
|
||||
return this.out.item.get(value.id) as ItemTarget[];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private addLocation(result: lsp.Location[], value: Range | lsp.Location, dedup: Set<string>): void {
|
||||
let location: lsp.Location;
|
||||
if (lsp.Location.is(value)) {
|
||||
location = value;
|
||||
} else {
|
||||
const document = this.in.contains.get(value.id)!;
|
||||
location = lsp.Location.create(this.fromDatabase((document as Document).uri), this.asRange(value));
|
||||
}
|
||||
const key = Locations.makeKey(location);
|
||||
if (!dedup.has(key)) {
|
||||
dedup.add(key);
|
||||
result.push(location);
|
||||
}
|
||||
}
|
||||
|
||||
private findRangesFromPosition(file: string, position: lsp.Position): Range[] | undefined {
|
||||
const value = this.indices.documents.get(file);
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const result: Range[] = [];
|
||||
for (const document of value.documents) {
|
||||
const id = document.id;
|
||||
const contains = this.out.contains.get(id);
|
||||
if (contains === undefined || contains.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let candidate: Range | undefined;
|
||||
for (const item of contains) {
|
||||
if (item.label !== VertexLabels.range) {
|
||||
continue;
|
||||
}
|
||||
if (JsonStore.containsPosition(item, position)) {
|
||||
if (!candidate) {
|
||||
candidate = item;
|
||||
} else {
|
||||
if (JsonStore.containsRange(candidate, item)) {
|
||||
candidate = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (candidate !== undefined) {
|
||||
result.push(candidate);
|
||||
}
|
||||
}
|
||||
return result.length > 0 ? result : undefined;
|
||||
}
|
||||
|
||||
private static containsPosition(range: lsp.Range, position: lsp.Position): boolean {
|
||||
if (position.line < range.start.line || position.line > range.end.line) {
|
||||
return false;
|
||||
}
|
||||
if (position.line === range.start.line && position.character < range.start.character) {
|
||||
return false;
|
||||
}
|
||||
if (position.line === range.end.line && position.character > range.end.character) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if `otherRange` is in `range`. If the ranges are equal, will return true.
|
||||
*/
|
||||
public static containsRange(range: lsp.Range, otherRange: lsp.Range): boolean {
|
||||
if (otherRange.start.line < range.start.line || otherRange.end.line < range.start.line) {
|
||||
return false;
|
||||
}
|
||||
if (otherRange.start.line > range.end.line || otherRange.end.line > range.end.line) {
|
||||
return false;
|
||||
}
|
||||
if (otherRange.start.line === range.start.line && otherRange.start.character < range.start.character) {
|
||||
return false;
|
||||
}
|
||||
if (otherRange.end.line === range.end.line && otherRange.end.character > range.end.character) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,30 @@
|
|||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import assert from 'assert';
|
||||
import { JsonStore } from '../jsonStore';
|
||||
import path from 'path';
|
||||
|
||||
suite('JSON Dump', async () => {
|
||||
|
||||
let store: JsonStore;
|
||||
|
||||
setup(async () => {
|
||||
store = new JsonStore();
|
||||
await store.load(path.join(__dirname, '..', '..', 'src', 'tests', 'dump.lsif'));
|
||||
});
|
||||
|
||||
test('document symbols', async () => {
|
||||
const symbols = store.documentSymbols('file:///lsif-node/protocol/src/protocol.ts');
|
||||
assert.ok(symbols !== undefined);
|
||||
assert.strictEqual(symbols!.length, 128);
|
||||
});
|
||||
|
||||
test('references', async () => {
|
||||
const references = store.references('file:///lsif-node/protocol/src/protocol.ts', { line: 7, character: 11 }, { includeDeclaration: true });
|
||||
assert.ok(references !== undefined);
|
||||
assert.strictEqual(references!.length, 10);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"composite": true,
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"tsBuildInfoFile":"./lib/compile.tsbuildInfo"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../protocol/tsconfig.json"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"references": [
|
||||
]
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"assumeChangesOnlyAffectDirectDependencies": true,
|
||||
"tsBuildInfoFile":"./lib/watch.tsbuildInfo"
|
||||
},
|
||||
"references": [
|
||||
{ "path": "../protocol/tsconfig.watch.json"}
|
||||
]
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
{ "path": "./tsc-tests/tsconfig.json" },
|
||||
{ "path": "./npm/tsconfig.json" },
|
||||
{ "path": "./sqlite/tsconfig.json" },
|
||||
{ "path": "./lsif/tsconfig.json" }
|
||||
{ "path": "./lsif/tsconfig.json" },
|
||||
{ "path": "./language-service/tsconfig.json"}
|
||||
]
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
{ "path": "./tsc-tests/tsconfig.watch.json" },
|
||||
{ "path": "./npm/tsconfig.watch.json" },
|
||||
{ "path": "./sqlite/tsconfig.watch.json" },
|
||||
{ "path": "./lsif/tsconfig.watch.json" }
|
||||
{ "path": "./lsif/tsconfig.watch.json" },
|
||||
{ "path": "./language-service/tsconfig.json"}
|
||||
]
|
||||
}
|
Загрузка…
Ссылка в новой задаче