Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(language-core): pluginized source map factory function #207

Merged
merged 1 commit into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -197,7 +197,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 @@ -208,7 +208,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 @@ -219,13 +219,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
Loading