Skip to content

Commit

Permalink
refactor(language-core): pluginized source map factory function (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk authored Jun 22, 2024
1 parent a00edb6 commit e9191e5
Show file tree
Hide file tree
Showing 13 changed files with 70 additions and 55 deletions.
4 changes: 2 additions & 2 deletions packages/eslint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ export function createProcessor(
messagesArr[i] = messagesArr[i].filter(message => {
const start = embeddedDocument.offsetAt({ line: message.line - 1, character: message.column - 1 });
const end = embeddedDocument.offsetAt({ line: (message.endLine ?? message.line) - 1, character: (message.endColumn ?? message.column) - 1 });
for (const [sourceStart, mapping] of map.getSourceOffsets(start)) {
for (const [sourceStart, mapping] of map.toSourceLocation(start)) {
if (isDiagnosticsEnabled(mapping.data)) {
for (const [sourceEnd, mapping] of map.getSourceOffsets(end)) {
for (const [sourceEnd, mapping] of map.toSourceLocation(end)) {
if (isDiagnosticsEnabled(mapping.data)) {
const sourcePosition = sourceDocument.positionAt(sourceStart);
const sourceEndPosition = sourceDocument.positionAt(sourceEnd);
Expand Down
21 changes: 13 additions & 8 deletions packages/language-core/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from '@volar/source-map';
export { Mapping } from '@volar/source-map';
export * from './lib/editorFeatures';
export * from './lib/linkedCodeMap';
export * from './lib/types';
Expand All @@ -8,24 +8,27 @@ import { SourceMap } from '@volar/source-map';
import type * as ts from 'typescript';
import { LinkedCodeMap } from './lib/linkedCodeMap';
import type {
CodeInformation,
CodegenContext,
Language,
LanguagePlugin,
Mapper,
MapperFactory,
SourceScript,
VirtualCode,
VirtualCode
} from './lib/types';

export const defaultMapperFactory: MapperFactory = mappings => new SourceMap(mappings);

export function createLanguage<T>(
plugins: LanguagePlugin<T>[],
scriptRegistry: Map<T, SourceScript<T>>,
sync: (id: T) => void
): Language<T> {
) {
const virtualCodeToSourceScriptMap = new WeakMap<VirtualCode, SourceScript<T>>();
const virtualCodeToSourceMap = new WeakMap<ts.IScriptSnapshot, WeakMap<ts.IScriptSnapshot, SourceMap<CodeInformation>>>();
const virtualCodeToSourceMap = new WeakMap<ts.IScriptSnapshot, WeakMap<ts.IScriptSnapshot, Mapper>>();
const virtualCodeToLinkedCodeMap = new WeakMap<ts.IScriptSnapshot, [ts.IScriptSnapshot, LinkedCodeMap | undefined]>();

return {
const language: Language<T> = {
mapperFactory: defaultMapperFactory,
plugins,
scripts: {
fromVirtualCode(virtualCode) {
Expand Down Expand Up @@ -154,7 +157,7 @@ export function createLanguage<T>(
const mappings = virtualCode.associatedScriptMappings?.get(sourceScript.id) ?? virtualCode.mappings;
mapCache.set(
sourceScript.snapshot,
new SourceMap(mappings)
language.mapperFactory(mappings)
);
}
return mapCache.get(sourceScript.snapshot)!;
Expand Down Expand Up @@ -198,6 +201,8 @@ export function createLanguage<T>(
},
};

return language;

function triggerTargetsDirty(sourceScript: SourceScript<T>) {
sourceScript.targetIds.forEach(id => {
const sourceScript = scriptRegistry.get(id);
Expand Down
4 changes: 2 additions & 2 deletions packages/language-core/lib/linkedCodeMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { SourceMap } from '@volar/source-map';

export class LinkedCodeMap extends SourceMap<any> {
*getLinkedOffsets(start: number) {
for (const mapped of this.getGeneratedOffsets(start)) {
for (const mapped of this.toGeneratedLocation(start)) {
yield mapped[0];
}
for (const mapped of this.getSourceOffsets(start)) {
for (const mapped of this.toSourceLocation(start)) {
yield mapped[0];
}
}
Expand Down
17 changes: 14 additions & 3 deletions packages/language-core/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import type { Mapping, SourceMap } from '@volar/source-map';
import type { Mapping } from '@volar/source-map';
import type * as ts from 'typescript';
import type { LinkedCodeMap } from './linkedCodeMap';

export interface Mapper {
mappings: Mapping<CodeInformation>[];
toSourceRange(start: number, end: number, fallbackToAnyMatch: boolean, filter?: (data: CodeInformation) => boolean): Generator<readonly [number, number, Mapping<CodeInformation>, Mapping<CodeInformation>]>;
toGeneratedRange(start: number, end: number, fallbackToAnyMatch: boolean, filter?: (data: CodeInformation) => boolean): Generator<readonly [number, number, Mapping<CodeInformation>, Mapping<CodeInformation>]>;
toSourceLocation(generatedOffset: number, filter?: (data: CodeInformation) => boolean): Generator<readonly [number, Mapping<CodeInformation>]>;
toGeneratedLocation(sourceOffset: number, filter?: (data: CodeInformation) => boolean): Generator<readonly [number, Mapping<CodeInformation>]>;
}

export type MapperFactory = (mappings: Mapping<CodeInformation>[]) => Mapper;

export interface Language<T = unknown> {
mapperFactory: MapperFactory;
plugins: LanguagePlugin<T>[];
scripts: {
get(id: T): SourceScript<T> | undefined;
Expand All @@ -11,8 +22,8 @@ export interface Language<T = unknown> {
fromVirtualCode(virtualCode: VirtualCode): SourceScript<T>;
};
maps: {
get(virtualCode: VirtualCode, sourceScript: SourceScript<T>): SourceMap<CodeInformation>;
forEach(virtualCode: VirtualCode): Generator<[sourceScript: SourceScript<T>, map: SourceMap<CodeInformation>]>;
get(virtualCode: VirtualCode, sourceScript: SourceScript<T>): Mapper;
forEach(virtualCode: VirtualCode): Generator<[sourceScript: SourceScript<T>, map: Mapper]>;
};
linkedCodeMaps: {
get(virtualCode: VirtualCode): LinkedCodeMap | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function register(context: LanguageServiceContext) {
() => ({ selection, change }),
function* (docs) {
for (const mappedPosition of getGeneratedPositions(docs, selection, isAutoInsertEnabled)) {
for (const mapped of docs[2].getGeneratedOffsets(change.rangeOffset)) {
for (const mapped of docs[2].toGeneratedLocation(change.rangeOffset)) {
yield {
selection: mappedPosition,
change: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SourceMap, SourceScript, VirtualCode, forEachEmbeddedCode, isFormattingEnabled } from '@volar/language-core';
import { SourceScript, VirtualCode, forEachEmbeddedCode, isFormattingEnabled } from '@volar/language-core';
import type * as ts from 'typescript';
import type * as vscode from 'vscode-languageserver-protocol';
import { TextDocument } from 'vscode-languageserver-textdocument';
Expand Down Expand Up @@ -250,7 +250,6 @@ export function register(context: LanguageServiceContext) {
};

function createDocMap(virtualCode: VirtualCode, documentUri: URI, sourceLanguageId: string, _sourceSnapshot: ts.IScriptSnapshot): DocumentsAndMap {
const map = new SourceMap(virtualCode.mappings);
const version = fakeVersion++;
return [
TextDocument.create(
Expand All @@ -265,7 +264,7 @@ export function register(context: LanguageServiceContext) {
version,
virtualCode.snapshot.getText(0, virtualCode.snapshot.getLength())
),
map,
context.language.mapperFactory(virtualCode.mappings),
];
}
}
Expand Down
8 changes: 4 additions & 4 deletions packages/language-service/lib/utils/common.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import type { CodeInformation, Mapper } from '@volar/language-core';
import type * as ts from 'typescript';
import type * as vscode from 'vscode-languageserver-protocol';
import type { CodeInformation, SourceMap } from '@volar/language-core';

export function findOverlapCodeRange(
start: number,
end: number,
map: SourceMap<CodeInformation>,
map: Mapper,
filter: (data: CodeInformation) => boolean
) {
let mappedStart: number | undefined;
let mappedEnd: number | undefined;

for (const [mapped, mapping] of map.getGeneratedOffsets(start)) {
for (const [mapped, mapping] of map.toGeneratedLocation(start)) {
if (filter(mapping.data)) {
mappedStart = mapped;
break;
}
}
for (const [mapped, mapping] of map.getGeneratedOffsets(end)) {
for (const [mapped, mapping] of map.toGeneratedLocation(end)) {
if (filter(mapping.data)) {
mappedEnd = mapped;
break;
Expand Down
12 changes: 6 additions & 6 deletions packages/language-service/lib/utils/featureWorkers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CodeInformation, LinkedCodeMap, SourceMap, SourceScript, VirtualCode } from '@volar/language-core';
import type { CodeInformation, LinkedCodeMap, Mapper, SourceScript, VirtualCode } from '@volar/language-core';
import type * as vscode from 'vscode-languageserver-protocol';
import type { TextDocument } from 'vscode-languageserver-textdocument';
import type { URI } from 'vscode-uri';
Expand All @@ -7,7 +7,7 @@ import type { LanguageServiceContext, LanguageServicePlugin, LanguageServicePlug
export type DocumentsAndMap = [
sourceDocument: TextDocument,
embeddedDocument: TextDocument,
map: SourceMap<CodeInformation>,
map: Mapper,
];

export function documentFeatureWorker<T>(
Expand Down Expand Up @@ -220,7 +220,7 @@ export function getGeneratedRange(docs: DocumentsAndMap, range: vscode.Range, fi
}

export function* getSourceRanges([sourceDocument, embeddedDocument, map]: DocumentsAndMap, range: vscode.Range, filter?: (data: CodeInformation) => boolean) {
for (const [mappedStart, mappedEnd] of map.getSourceStartEnd(
for (const [mappedStart, mappedEnd] of map.toSourceRange(
embeddedDocument.offsetAt(range.start),
embeddedDocument.offsetAt(range.end),
true,
Expand All @@ -231,7 +231,7 @@ export function* getSourceRanges([sourceDocument, embeddedDocument, map]: Docume
}

export function* getGeneratedRanges([sourceDocument, embeddedDocument, map]: DocumentsAndMap, range: vscode.Range, filter?: (data: CodeInformation) => boolean) {
for (const [mappedStart, mappedEnd] of map.getGeneratedStartEnd(
for (const [mappedStart, mappedEnd] of map.toGeneratedRange(
sourceDocument.offsetAt(range.start),
sourceDocument.offsetAt(range.end),
true,
Expand All @@ -242,13 +242,13 @@ export function* getGeneratedRanges([sourceDocument, embeddedDocument, map]: Doc
}

export function* getSourcePositions([sourceDocument, embeddedDocument, map]: DocumentsAndMap, position: vscode.Position, filter: (data: CodeInformation) => boolean = () => true) {
for (const mapped of map.getSourceOffsets(embeddedDocument.offsetAt(position), filter)) {
for (const mapped of map.toSourceLocation(embeddedDocument.offsetAt(position), filter)) {
yield sourceDocument.positionAt(mapped[0]);
}
}

export function* getGeneratedPositions([sourceDocument, embeddedDocument, map]: DocumentsAndMap, position: vscode.Position, filter: (data: CodeInformation) => boolean = () => true) {
for (const mapped of map.getGeneratedOffsets(sourceDocument.offsetAt(position), filter)) {
for (const mapped of map.toGeneratedLocation(sourceDocument.offsetAt(position), filter)) {
yield embeddedDocument.positionAt(mapped[0]);
}
}
Expand Down
12 changes: 6 additions & 6 deletions packages/language-service/tests/findOverlapCodeRange.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { findOverlapCodeRange } from '../lib/utils/common';
import { CodeInformation, Mapping, SourceMap } from '@volar/language-core';
import { CodeInformation, Mapping, defaultMapperFactory } from '@volar/language-core';

// test code: <html><body><p>Hello</p></body></html>

Expand All @@ -15,7 +15,7 @@ describe(`Test findOverlapCodeRange()`, () => {
data: {},
},
];
const map = new SourceMap(mappings);
const map = defaultMapperFactory(mappings);

expect(findOverlapCodeRange(0, 38, map, () => true)).toEqual({ start: 0, end: 38 });
expect(findOverlapCodeRange(6, 31, map, () => true)).toEqual({ start: 6, end: 31 });
Expand All @@ -30,7 +30,7 @@ describe(`Test findOverlapCodeRange()`, () => {
data: {},
},
];
const map = new SourceMap(mappings);
const map = defaultMapperFactory(mappings);

expect(findOverlapCodeRange(5, 32, map, () => true)).toEqual({ start: 6, end: 31 });
expect(findOverlapCodeRange(7, 32, map, () => true)).toEqual({ start: 7, end: 31 });
Expand All @@ -46,7 +46,7 @@ describe(`Test findOverlapCodeRange()`, () => {
data: {},
},
];
const map = new SourceMap(mappings);
const map = defaultMapperFactory(mappings);

expect(findOverlapCodeRange(5, 32, map, () => true)).toEqual({ start: 7, end: 32 });
expect(findOverlapCodeRange(7, 32, map, () => true)).toEqual({ start: 8, end: 32 });
Expand All @@ -63,7 +63,7 @@ describe(`Test findOverlapCodeRange()`, () => {
data: {},
},
];
const map = new SourceMap(mappings);
const map = defaultMapperFactory(mappings);

expect(findOverlapCodeRange(5, 32, map, () => true)).toEqual({ start: 7, end: 30 });
expect(findOverlapCodeRange(7, 32, map, () => true)).toEqual({ start: 8, end: 30 });
Expand All @@ -87,7 +87,7 @@ describe(`Test findOverlapCodeRange()`, () => {
data: {},
},
];
const map = new SourceMap(mappings);
const map = defaultMapperFactory(mappings);

expect(findOverlapCodeRange(0, 38, map, () => true)).toEqual({ start: 6, end: 33 });
});
Expand Down
8 changes: 4 additions & 4 deletions packages/source-map/lib/sourceMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@ export class SourceMap<Data = unknown> {

constructor(public readonly mappings: Mapping<Data>[]) { }

getSourceStartEnd(generatedStart: number, generatedEnd: number, fallbackToAnyMatch: boolean, filter?: (data: Data) => boolean) {
toSourceRange(generatedStart: number, generatedEnd: number, fallbackToAnyMatch: boolean, filter?: (data: Data) => boolean) {
return this.findMatchingStartEnd(generatedStart, generatedEnd, fallbackToAnyMatch, 'generatedOffsets', filter);
}

getGeneratedStartEnd(sourceStart: number, sourceEnd: number, fallbackToAnyMatch: boolean, filter?: (data: Data) => boolean) {
toGeneratedRange(sourceStart: number, sourceEnd: number, fallbackToAnyMatch: boolean, filter?: (data: Data) => boolean) {
return this.findMatchingStartEnd(sourceStart, sourceEnd, fallbackToAnyMatch, 'sourceOffsets', filter);
}

getSourceOffsets(generatedOffset: number, filter?: (data: Data) => boolean) {
toSourceLocation(generatedOffset: number, filter?: (data: Data) => boolean) {
return this.findMatchingOffsets(generatedOffset, 'generatedOffsets', filter);
}

getGeneratedOffsets(sourceOffset: number, filter?: (data: Data) => boolean) {
toGeneratedLocation(sourceOffset: number, filter?: (data: Data) => boolean) {
return this.findMatchingOffsets(sourceOffset, 'sourceOffsets', filter);
}

Expand Down
14 changes: 7 additions & 7 deletions packages/source-map/tests/sourceMap.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ describe('sourceMap', () => {
},
]);

expect([...map.getGeneratedStartEnd(
expect([...map.toGeneratedRange(
`{{|data?.icon?.toString()}}`.indexOf('|'),
`{{data|?.icon?.toString()}}`.indexOf('|'),
false
Expand All @@ -121,13 +121,13 @@ describe('sourceMap', () => {
],
]);

expect([...map.getGeneratedStartEnd(
expect([...map.toGeneratedRange(
`{{|data?.icon?.toString()}}`.indexOf('|'),
`{{data?.ic|on?.toString()}}`.indexOf('|'),
false
)].map(mapped => mapped.slice(0, 2))).toEqual([]);

expect([...map.getGeneratedStartEnd(
expect([...map.toGeneratedRange(
`{{|data?.icon?.toString()}}`.indexOf('|'),
`{{data?.icon|?.toString()}}`.indexOf('|'),
false
Expand All @@ -140,7 +140,7 @@ describe('sourceMap', () => {
],
]);

expect([...map.getGeneratedStartEnd(
expect([...map.toGeneratedRange(
`{{|data?.icon?.toString()}}`.indexOf('|'),
`{{data?.icon?.toString|()}}`.indexOf('|'),
false
Expand All @@ -153,7 +153,7 @@ describe('sourceMap', () => {
],
]);

expect([...map.getGeneratedStartEnd(
expect([...map.toGeneratedRange(
`{{|data?.icon?.toString()}}`.indexOf('|'),
`{{data?.icon?.toString()|}}`.indexOf('|'),
false
Expand Down Expand Up @@ -193,13 +193,13 @@ describe('sourceMap', () => {
},
]);

expect([...map.getGeneratedStartEnd(
expect([...map.toGeneratedRange(
`{{|data?.icon?.toString()}}`.indexOf('|'),
`{{data?.icon|?.toString()}}`.indexOf('|'),
false
)].map(mapped => mapped.slice(0, 2))).toEqual([]);

expect([...map.getGeneratedStartEnd(
expect([...map.toGeneratedRange(
`{{|data?.icon?.toString()}}`.indexOf('|'),
`{{data?.icon|?.toString()}}`.indexOf('|'),
true
Expand Down
6 changes: 3 additions & 3 deletions packages/test-utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { defaultMapperFactory, forEachEmbeddedCode } from '@volar/language-core';
import * as _ from '@volar/language-server/node';
import * as assert from 'assert';
import * as cp from 'child_process';
import * as fs from 'fs';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { URI } from 'vscode-uri';
import { SourceMap, forEachEmbeddedCode } from '@volar/language-core';

export type LanguageServerHandle = ReturnType<typeof startLanguageServer>;

Expand Down Expand Up @@ -449,7 +449,7 @@ export function* printSnapshot(

let lineOffset = 0;

const map = new SourceMap(file.mappings);
const map = defaultMapperFactory(file.mappings);

for (let i = 0; i < virtualCodeLines.length; i++) {
const line = virtualCodeLines[i];
Expand All @@ -464,7 +464,7 @@ export function* printSnapshot(
length: number;
}[] = [];
for (let offset = 0; offset < line.length; offset++) {
for (const [sourceOffset, mapping] of map.getSourceOffsets(lineOffset + offset)) {
for (const [sourceOffset, mapping] of map.toSourceLocation(lineOffset + offset)) {
let log = logs.find(log => log.mapping === mapping && log.lineOffset + log.length + 1 === offset);
if (log) {
log.length++;
Expand Down
Loading

0 comments on commit e9191e5

Please sign in to comment.