diff --git a/packages/eslint/index.ts b/packages/eslint/index.ts index c07fa6b7..e4d12e98 100644 --- a/packages/eslint/index.ts +++ b/packages/eslint/index.ts @@ -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); diff --git a/packages/language-core/index.ts b/packages/language-core/index.ts index 9d8dde6b..a33924b4 100644 --- a/packages/language-core/index.ts +++ b/packages/language-core/index.ts @@ -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'; @@ -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( plugins: LanguagePlugin[], scriptRegistry: Map>, sync: (id: T) => void -): Language { +) { const virtualCodeToSourceScriptMap = new WeakMap>(); - const virtualCodeToSourceMap = new WeakMap>>(); + const virtualCodeToSourceMap = new WeakMap>(); const virtualCodeToLinkedCodeMap = new WeakMap(); - - return { + const language: Language = { + mapperFactory: defaultMapperFactory, plugins, scripts: { fromVirtualCode(virtualCode) { @@ -154,7 +157,7 @@ export function createLanguage( const mappings = virtualCode.associatedScriptMappings?.get(sourceScript.id) ?? virtualCode.mappings; mapCache.set( sourceScript.snapshot, - new SourceMap(mappings) + language.mapperFactory(mappings) ); } return mapCache.get(sourceScript.snapshot)!; @@ -198,6 +201,8 @@ export function createLanguage( }, }; + return language; + function triggerTargetsDirty(sourceScript: SourceScript) { sourceScript.targetIds.forEach(id => { const sourceScript = scriptRegistry.get(id); diff --git a/packages/language-core/lib/linkedCodeMap.ts b/packages/language-core/lib/linkedCodeMap.ts index eac2bf49..652bb6f8 100644 --- a/packages/language-core/lib/linkedCodeMap.ts +++ b/packages/language-core/lib/linkedCodeMap.ts @@ -2,10 +2,10 @@ import { SourceMap } from '@volar/source-map'; export class LinkedCodeMap extends SourceMap { *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]; } } diff --git a/packages/language-core/lib/types.ts b/packages/language-core/lib/types.ts index 0d943297..3027cdf7 100644 --- a/packages/language-core/lib/types.ts +++ b/packages/language-core/lib/types.ts @@ -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[]; + toSourceRange(start: number, end: number, fallbackToAnyMatch: boolean, filter?: (data: CodeInformation) => boolean): Generator, Mapping]>; + toGeneratedRange(start: number, end: number, fallbackToAnyMatch: boolean, filter?: (data: CodeInformation) => boolean): Generator, Mapping]>; + toSourceLocation(generatedOffset: number, filter?: (data: CodeInformation) => boolean): Generator]>; + toGeneratedLocation(sourceOffset: number, filter?: (data: CodeInformation) => boolean): Generator]>; +} + +export type MapperFactory = (mappings: Mapping[]) => Mapper; + export interface Language { + mapperFactory: MapperFactory; plugins: LanguagePlugin[]; scripts: { get(id: T): SourceScript | undefined; @@ -11,8 +22,8 @@ export interface Language { fromVirtualCode(virtualCode: VirtualCode): SourceScript; }; maps: { - get(virtualCode: VirtualCode, sourceScript: SourceScript): SourceMap; - forEach(virtualCode: VirtualCode): Generator<[sourceScript: SourceScript, map: SourceMap]>; + get(virtualCode: VirtualCode, sourceScript: SourceScript): Mapper; + forEach(virtualCode: VirtualCode): Generator<[sourceScript: SourceScript, map: Mapper]>; }; linkedCodeMaps: { get(virtualCode: VirtualCode): LinkedCodeMap | undefined; diff --git a/packages/language-service/lib/features/provideAutoInsertSnippet.ts b/packages/language-service/lib/features/provideAutoInsertSnippet.ts index 61d9b2cd..87cea3c4 100644 --- a/packages/language-service/lib/features/provideAutoInsertSnippet.ts +++ b/packages/language-service/lib/features/provideAutoInsertSnippet.ts @@ -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: { diff --git a/packages/language-service/lib/features/provideDocumentFormattingEdits.ts b/packages/language-service/lib/features/provideDocumentFormattingEdits.ts index 4f109255..5010deb7 100644 --- a/packages/language-service/lib/features/provideDocumentFormattingEdits.ts +++ b/packages/language-service/lib/features/provideDocumentFormattingEdits.ts @@ -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'; @@ -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( @@ -265,7 +264,7 @@ export function register(context: LanguageServiceContext) { version, virtualCode.snapshot.getText(0, virtualCode.snapshot.getLength()) ), - map, + context.language.mapperFactory(virtualCode.mappings), ]; } } diff --git a/packages/language-service/lib/utils/common.ts b/packages/language-service/lib/utils/common.ts index 34789b3c..92737719 100644 --- a/packages/language-service/lib/utils/common.ts +++ b/packages/language-service/lib/utils/common.ts @@ -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, + 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; diff --git a/packages/language-service/lib/utils/featureWorkers.ts b/packages/language-service/lib/utils/featureWorkers.ts index 250bba42..cd6faf3c 100644 --- a/packages/language-service/lib/utils/featureWorkers.ts +++ b/packages/language-service/lib/utils/featureWorkers.ts @@ -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'; @@ -7,7 +7,7 @@ import type { LanguageServiceContext, LanguageServicePlugin, LanguageServicePlug export type DocumentsAndMap = [ sourceDocument: TextDocument, embeddedDocument: TextDocument, - map: SourceMap, + map: Mapper, ]; export function documentFeatureWorker( @@ -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, @@ -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, @@ -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]); } } diff --git a/packages/language-service/tests/findOverlapCodeRange.spec.ts b/packages/language-service/tests/findOverlapCodeRange.spec.ts index 61b5f3ff..402f1266 100644 --- a/packages/language-service/tests/findOverlapCodeRange.spec.ts +++ b/packages/language-service/tests/findOverlapCodeRange.spec.ts @@ -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:

Hello

@@ -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 }); @@ -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 }); @@ -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 }); @@ -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 }); @@ -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 }); }); diff --git a/packages/source-map/lib/sourceMap.ts b/packages/source-map/lib/sourceMap.ts index c95e09ef..1577f4a8 100644 --- a/packages/source-map/lib/sourceMap.ts +++ b/packages/source-map/lib/sourceMap.ts @@ -23,19 +23,19 @@ export class SourceMap { constructor(public readonly mappings: Mapping[]) { } - 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); } diff --git a/packages/source-map/tests/sourceMap.spec.ts b/packages/source-map/tests/sourceMap.spec.ts index a154e87a..ed0db1ed 100644 --- a/packages/source-map/tests/sourceMap.spec.ts +++ b/packages/source-map/tests/sourceMap.spec.ts @@ -108,7 +108,7 @@ describe('sourceMap', () => { }, ]); - expect([...map.getGeneratedStartEnd( + expect([...map.toGeneratedRange( `{{|data?.icon?.toString()}}`.indexOf('|'), `{{data|?.icon?.toString()}}`.indexOf('|'), false @@ -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 @@ -140,7 +140,7 @@ describe('sourceMap', () => { ], ]); - expect([...map.getGeneratedStartEnd( + expect([...map.toGeneratedRange( `{{|data?.icon?.toString()}}`.indexOf('|'), `{{data?.icon?.toString|()}}`.indexOf('|'), false @@ -153,7 +153,7 @@ describe('sourceMap', () => { ], ]); - expect([...map.getGeneratedStartEnd( + expect([...map.toGeneratedRange( `{{|data?.icon?.toString()}}`.indexOf('|'), `{{data?.icon?.toString()|}}`.indexOf('|'), false @@ -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 diff --git a/packages/test-utils/index.ts b/packages/test-utils/index.ts index abccba27..3441c378 100644 --- a/packages/test-utils/index.ts +++ b/packages/test-utils/index.ts @@ -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; @@ -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]; @@ -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++; diff --git a/packages/typescript/lib/node/transform.ts b/packages/typescript/lib/node/transform.ts index 2e5a5b38..75edda12 100644 --- a/packages/typescript/lib/node/transform.ts +++ b/packages/typescript/lib/node/transform.ts @@ -245,7 +245,7 @@ export function* toSourceRanges( ): Generator<[fileName: string, start: number, end: number]> { if (sourceScript) { const map = language.maps.get(serviceScript.code, sourceScript); - for (const [sourceStart, sourceEnd] of map.getSourceStartEnd( + for (const [sourceStart, sourceEnd] of map.toSourceRange( start - getMappingOffset(language, serviceScript), end - getMappingOffset(language, serviceScript), true, @@ -256,7 +256,7 @@ export function* toSourceRanges( } else { for (const [sourceScript, map] of language.maps.forEach(serviceScript.code)) { - for (const [sourceStart, sourceEnd] of map.getSourceStartEnd( + for (const [sourceStart, sourceEnd] of map.toSourceRange( start - getMappingOffset(language, serviceScript), end - getMappingOffset(language, serviceScript), true, @@ -277,7 +277,7 @@ export function* toSourceOffsets( ): Generator<[fileName: string, offset: number]> { if (sourceScript) { const map = language.maps.get(serviceScript.code, sourceScript); - for (const [sourceOffset, mapping] of map.getSourceOffsets(position - getMappingOffset(language, serviceScript))) { + for (const [sourceOffset, mapping] of map.toSourceLocation(position - getMappingOffset(language, serviceScript))) { if (filter(mapping.data)) { yield [sourceScript.id, sourceOffset]; } @@ -285,7 +285,7 @@ export function* toSourceOffsets( } else { for (const [sourceScript, map] of language.maps.forEach(serviceScript.code)) { - for (const [sourceOffset, mapping] of map.getSourceOffsets(position - getMappingOffset(language, serviceScript))) { + for (const [sourceOffset, mapping] of map.toSourceLocation(position - getMappingOffset(language, serviceScript))) { if (filter(mapping.data)) { yield [sourceScript.id, sourceOffset]; } @@ -303,7 +303,7 @@ export function* toGeneratedRanges( filter: (data: CodeInformation) => boolean ) { const map = language.maps.get(serviceScript.code, sourceScript); - for (const [generateStart, generateEnd] of map.getGeneratedStartEnd(start, end, true, filter)) { + for (const [generateStart, generateEnd] of map.toGeneratedRange(start, end, true, filter)) { yield [ generateStart + getMappingOffset(language, serviceScript), generateEnd + getMappingOffset(language, serviceScript), @@ -331,7 +331,7 @@ export function* toGeneratedOffsets( filter: (data: CodeInformation) => boolean ) { const map = language.maps.get(serviceScript.code, sourceScript); - for (const [generateOffset, mapping] of map.getGeneratedOffsets(position)) { + for (const [generateOffset, mapping] of map.toGeneratedLocation(position)) { if (filter(mapping.data)) { yield [generateOffset + getMappingOffset(language, serviceScript), mapping] as const; }