From 3da620f890efe54e2194e7f0dc4cedb7aea7ae78 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 4 Jun 2024 04:04:54 +0800 Subject: [PATCH] feat(typescript): add option to prevent offset in plugin mode --- packages/language-core/lib/types.ts | 2 + .../lib/node/decorateLanguageService.ts | 94 +++++++++++-------- .../lib/node/decorateLanguageServiceHost.ts | 29 ++++-- .../typescript/lib/node/proxyCreateProgram.ts | 8 +- packages/typescript/lib/node/transform.ts | 62 ++++++++---- 5 files changed, 126 insertions(+), 69 deletions(-) diff --git a/packages/language-core/lib/types.ts b/packages/language-core/lib/types.ts index abd38b51..c849755d 100644 --- a/packages/language-core/lib/types.ts +++ b/packages/language-core/lib/types.ts @@ -81,6 +81,8 @@ export interface TypeScriptServiceScript { code: VirtualCode; extension: '.ts' | '.js' | '.mts' | '.mjs' | '.cjs' | '.cts' | '.d.ts' | string; scriptKind: ts.ScriptKind; + /** See #188 */ + preventLeadingOffset?: boolean; } export interface TypeScriptExtraServiceScript extends TypeScriptServiceScript { diff --git a/packages/typescript/lib/node/decorateLanguageService.ts b/packages/typescript/lib/node/decorateLanguageService.ts index a149fba2..8954511d 100644 --- a/packages/typescript/lib/node/decorateLanguageService.ts +++ b/packages/typescript/lib/node/decorateLanguageService.ts @@ -19,8 +19,20 @@ import { } from '@volar/language-core'; import type * as ts from 'typescript'; import { dedupeDocumentSpans } from './dedupe'; +import { + getMappingOffset, + toGeneratedOffset, + toGeneratedOffsets, + toSourceOffset, + transformCallHierarchyItem, + transformDiagnostic, + transformDocumentSpan, + transformFileTextChanges, + transformSpan, + transformTextChange, + transformTextSpan, +} from './transform'; import { getServiceScript, notEmpty } from './utils'; -import { toGeneratedOffsets, toGeneratedOffset, toSourceOffset, transformCallHierarchyItem, transformDiagnostic, transformDocumentSpan, transformFileTextChanges, transformSpan, transformTextChange, transformTextSpan } from './transform'; const windowsPathReg = /\\/g; @@ -104,7 +116,7 @@ export function decorateLanguageService( } const edits = getFormattingEditsForDocument(fileName, options); return edits - .map(edit => transformTextChange(sourceScript, map, edit, isFormattingEnabled)) + .map(edit => transformTextChange(serviceScript, sourceScript, map, edit, isFormattingEnabled)) .filter(notEmpty); } else { @@ -115,12 +127,12 @@ export function decorateLanguageService( const fileName = filePath.replace(windowsPathReg, '/'); const [serviceScript, sourceScript, map] = getServiceScript(language, fileName); if (serviceScript) { - const generateStart = toGeneratedOffset(sourceScript, map, start, isFormattingEnabled); - const generateEnd = toGeneratedOffset(sourceScript, map, end, isFormattingEnabled); + const generateStart = toGeneratedOffset(serviceScript, sourceScript, map, start, isFormattingEnabled); + const generateEnd = toGeneratedOffset(serviceScript, sourceScript, map, end, isFormattingEnabled); if (generateStart !== undefined && generateEnd !== undefined) { const edits = getFormattingEditsForRange(fileName, generateStart, generateEnd, options); return edits - .map(edit => transformTextChange(sourceScript, map, edit, isFormattingEnabled)) + .map(edit => transformTextChange(serviceScript, sourceScript, map, edit, isFormattingEnabled)) .filter(notEmpty); } return []; @@ -133,11 +145,11 @@ export function decorateLanguageService( const fileName = filePath.replace(windowsPathReg, '/'); const [serviceScript, sourceScript, map] = getServiceScript(language, fileName); if (serviceScript) { - const generatePosition = toGeneratedOffset(sourceScript, map, position, isFormattingEnabled); + const generatePosition = toGeneratedOffset(serviceScript, sourceScript, map, position, isFormattingEnabled); if (generatePosition !== undefined) { const edits = getFormattingEditsAfterKeystroke(fileName, generatePosition, key, options); return edits - .map(edit => transformTextChange(sourceScript, map, edit, isFormattingEnabled)) + .map(edit => transformTextChange(serviceScript, sourceScript, map, edit, isFormattingEnabled)) .filter(notEmpty); } return []; @@ -156,13 +168,13 @@ export function decorateLanguageService( const fileName = filePath.replace(windowsPathReg, '/'); const [serviceScript, sourceScript, map] = getServiceScript(language, fileName); if (serviceScript) { - const generatePosition = toGeneratedOffset(sourceScript, map, position, isLinkedEditingEnabled); + const generatePosition = toGeneratedOffset(serviceScript, sourceScript, map, position, isLinkedEditingEnabled); if (generatePosition !== undefined) { const info = getLinkedEditingRangeAtPosition(fileName, generatePosition); if (info) { return { ranges: info.ranges - .map(span => transformTextSpan(sourceScript, map, span, isLinkedEditingEnabled)) + .map(span => transformTextSpan(serviceScript, sourceScript, map, span, isLinkedEditingEnabled)) .filter(notEmpty), wordPattern: info.wordPattern, }; @@ -177,7 +189,7 @@ export function decorateLanguageService( const fileName = filePath.replace(windowsPathReg, '/'); const [serviceScript, sourceScript, map] = getServiceScript(language, fileName); if (serviceScript) { - const generatePosition = toGeneratedOffset(sourceScript, map, position, isCallHierarchyEnabled); + const generatePosition = toGeneratedOffset(serviceScript, sourceScript, map, position, isCallHierarchyEnabled); if (generatePosition !== undefined) { const item = prepareCallHierarchy(fileName, generatePosition); if (Array.isArray(item)) { @@ -197,7 +209,7 @@ export function decorateLanguageService( const fileName = filePath.replace(windowsPathReg, '/'); const [serviceScript, sourceScript, map] = getServiceScript(language, fileName); if (serviceScript) { - const generatePosition = toGeneratedOffset(sourceScript, map, position, isCallHierarchyEnabled); + const generatePosition = toGeneratedOffset(serviceScript, sourceScript, map, position, isCallHierarchyEnabled); if (generatePosition !== undefined) { calls = provideCallHierarchyIncomingCalls(fileName, generatePosition); } @@ -222,7 +234,7 @@ export function decorateLanguageService( const fileName = filePath.replace(windowsPathReg, '/'); const [serviceScript, sourceScript, map] = getServiceScript(language, fileName); if (serviceScript) { - const generatePosition = toGeneratedOffset(sourceScript, map, position, isCallHierarchyEnabled); + const generatePosition = toGeneratedOffset(serviceScript, sourceScript, map, position, isCallHierarchyEnabled); if (generatePosition !== undefined) { calls = provideCallHierarchyOutgoingCalls(fileName, generatePosition); } @@ -235,7 +247,7 @@ export function decorateLanguageService( const to = transformCallHierarchyItem(language, call.to, isCallHierarchyEnabled); const fromSpans = call.fromSpans .map(span => sourceScript - ? transformTextSpan(sourceScript, map, span, isCallHierarchyEnabled) + ? transformTextSpan(serviceScript, sourceScript, map, span, isCallHierarchyEnabled) : span ) .filter(notEmpty); @@ -257,13 +269,13 @@ export function decorateLanguageService( const [serviceScript, sourceScript, map] = getServiceScript(language, fileName); if (serviceScript) { const infos: ts.QuickInfo[] = []; - for (const [generatePosition, mapping] of toGeneratedOffsets(sourceScript, map, position)) { + for (const [generatePosition, mapping] of toGeneratedOffsets(serviceScript, sourceScript, map, position)) { if (!isHoverEnabled(mapping.data)) { continue; } const info = getQuickInfoAtPosition(fileName, generatePosition); if (info) { - const textSpan = transformTextSpan(sourceScript, map, info.textSpan, isHoverEnabled); + const textSpan = transformTextSpan(serviceScript, sourceScript, map, info.textSpan, isHoverEnabled); if (textSpan) { infos.push({ ...info, @@ -319,11 +331,11 @@ export function decorateLanguageService( const fileName = filePath.replace(windowsPathReg, '/'); const [serviceScript, sourceScript, map] = getServiceScript(language, fileName); if (serviceScript) { - const generatePosition = toGeneratedOffset(sourceScript, map, position, isSignatureHelpEnabled); + const generatePosition = toGeneratedOffset(serviceScript, sourceScript, map, position, isSignatureHelpEnabled); if (generatePosition !== undefined) { const result = getSignatureHelpItems(fileName, generatePosition, options); if (result) { - const applicableSpan = transformTextSpan(sourceScript, map, result.applicableSpan, isSignatureHelpEnabled); + const applicableSpan = transformTextSpan(serviceScript, sourceScript, map, result.applicableSpan, isSignatureHelpEnabled); if (applicableSpan) { return { ...result, @@ -377,7 +389,7 @@ export function decorateLanguageService( const fileName = filePath.replace(windowsPathReg, '/'); const [serviceScript, sourceScript, map] = getServiceScript(language, fileName); if (serviceScript) { - const generatePosition = toGeneratedOffset(sourceScript, map, typeof positionOrRange === 'number' ? positionOrRange : positionOrRange.pos, isCodeActionsEnabled); + const generatePosition = toGeneratedOffset(serviceScript, sourceScript, map, typeof positionOrRange === 'number' ? positionOrRange : positionOrRange.pos, isCodeActionsEnabled); if (generatePosition !== undefined) { const por = typeof positionOrRange === 'number' ? generatePosition @@ -399,6 +411,7 @@ export function decorateLanguageService( const [serviceScript, sourceScript, map] = getServiceScript(language, fileName); if (serviceScript) { const generatePosition = toGeneratedOffset( + serviceScript, sourceScript, map, typeof positionOrRange === 'number' @@ -431,13 +444,13 @@ export function decorateLanguageService( const [serviceScript, sourceScript, map] = getServiceScript(language, fileName); if (serviceScript) { let failed: ts.RenameInfoFailure | undefined; - for (const [generateOffset, mapping] of toGeneratedOffsets(sourceScript, map, position)) { + for (const [generateOffset, mapping] of toGeneratedOffsets(serviceScript, sourceScript, map, position)) { if (!isRenameEnabled(mapping.data)) { continue; } const info = getRenameInfo(fileName, generateOffset, options); if (info.canRename) { - const span = transformTextSpan(sourceScript, map, info.triggerSpan, isRenameEnabled); + const span = transformTextSpan(serviceScript, sourceScript, map, info.triggerSpan, isRenameEnabled); if (span) { info.triggerSpan = span; return info; @@ -464,8 +477,8 @@ export function decorateLanguageService( const fileName = filePath.replace(windowsPathReg, '/'); const [serviceScript, sourceScript, map] = getServiceScript(language, fileName); if (serviceScript) { - const generateStart = toGeneratedOffset(sourceScript, map, start, isCodeActionsEnabled); - const generateEnd = toGeneratedOffset(sourceScript, map, end, isCodeActionsEnabled); + const generateStart = toGeneratedOffset(serviceScript, sourceScript, map, start, isCodeActionsEnabled); + const generateEnd = toGeneratedOffset(serviceScript, sourceScript, map, end, isCodeActionsEnabled); if (generateStart !== undefined && generateEnd !== undefined) { fixes = getCodeFixesAtPosition( fileName, @@ -503,13 +516,14 @@ export function decorateLanguageService( } start ??= 0; end ??= sourceScript.snapshot.getLength(); - start += sourceScript.snapshot.getLength(); - end += sourceScript.snapshot.getLength(); + const mappingOffset = getMappingOffset(serviceScript, sourceScript); + start += mappingOffset; + end += mappingOffset; const result = getEncodedSemanticClassifications(fileName, { start, length: end - start }, format); const spans: number[] = []; for (let i = 0; i < result.spans.length; i += 3) { - const sourceStart = toSourceOffset(sourceScript, map, result.spans[i], isSemanticTokensEnabled); - const sourceEnd = toSourceOffset(sourceScript, map, result.spans[i] + result.spans[i + 1], isSemanticTokensEnabled); + const sourceStart = toSourceOffset(serviceScript, sourceScript, map, result.spans[i], isSemanticTokensEnabled); + const sourceEnd = toSourceOffset(serviceScript, sourceScript, map, result.spans[i] + result.spans[i + 1], isSemanticTokensEnabled); if (sourceStart !== undefined && sourceEnd !== undefined) { spans.push( sourceStart, @@ -702,7 +716,7 @@ export function decorateLanguageService( const [serviceScript, sourceScript, map] = getServiceScript(language, fileName); if (serviceScript) { const results: ts.CompletionInfo[] = []; - for (const [generatedOffset, mapping] of toGeneratedOffsets(sourceScript, map, position)) { + for (const [generatedOffset, mapping] of toGeneratedOffsets(serviceScript, sourceScript, map, position)) { if (!isCompletionEnabled(mapping.data)) { continue; } @@ -714,10 +728,10 @@ export function decorateLanguageService( result.entries = result.entries.filter(entry => !!entry.sourceDisplay); } for (const entry of result.entries) { - entry.replacementSpan = entry.replacementSpan && transformTextSpan(sourceScript, map, entry.replacementSpan, isCompletionEnabled); + entry.replacementSpan = entry.replacementSpan && transformTextSpan(serviceScript, sourceScript, map, entry.replacementSpan, isCompletionEnabled); } result.optionalReplacementSpan = result.optionalReplacementSpan - && transformTextSpan(sourceScript, map, result.optionalReplacementSpan, isCompletionEnabled); + && transformTextSpan(serviceScript, sourceScript, map, result.optionalReplacementSpan, isCompletionEnabled); const isAdditional = typeof mapping.data.completion === 'object' && mapping.data.completion.isAdditional; if (isAdditional) { results.push(result); @@ -746,7 +760,7 @@ export function decorateLanguageService( const fileName = filePath.replace(windowsPathReg, '/'); const [serviceScript, sourceScript, map] = getServiceScript(language, fileName); if (serviceScript) { - const generatePosition = toGeneratedOffset(sourceScript, map, position, isCompletionEnabled); + const generatePosition = toGeneratedOffset(serviceScript, sourceScript, map, position, isCompletionEnabled); if (generatePosition !== undefined) { details = getCompletionEntryDetails(fileName, generatePosition, entryName, formatOptions, source, preferences, data); } @@ -781,12 +795,13 @@ export function decorateLanguageService( start = 0; end = 0; } - start += sourceScript.snapshot.getLength(); - end += sourceScript.snapshot.getLength(); + const mappingOffset = getMappingOffset(serviceScript, sourceScript); + start += mappingOffset; + end += mappingOffset; const result = provideInlayHints(fileName, { start, length: end - start }, preferences); const hints: ts.InlayHint[] = []; for (const hint of result) { - const sourcePosition = toSourceOffset(sourceScript, map, hint.position, isInlayHintsEnabled); + const sourcePosition = toSourceOffset(serviceScript, sourceScript, map, hint.position, isInlayHintsEnabled); if (sourcePosition !== undefined) { hints.push({ ...hint, @@ -822,7 +837,7 @@ export function decorateLanguageService( if (serviceScript) { for (const [generatedOffset, mapping] of map.getGeneratedOffsets(position)) { if (filter(mapping.data)) { - process(fileName, generatedOffset + sourceScript.snapshot.getLength()); + process(fileName, generatedOffset + getMappingOffset(serviceScript, sourceScript)); } } } @@ -846,18 +861,19 @@ export function decorateLanguageService( processedFilePositions.add(ref[0] + ':' + ref[1]); - const [virtualFile, sourceScript] = getServiceScript(language, ref[0]); - if (!virtualFile) { + const [serviceScript, sourceScript] = getServiceScript(language, ref[0]); + if (!serviceScript) { continue; } - const linkedCodeMap = language.linkedCodeMaps.get(virtualFile.code); + const linkedCodeMap = language.linkedCodeMaps.get(serviceScript.code); if (!linkedCodeMap) { continue; } - for (const linkedCodeOffset of linkedCodeMap.getLinkedOffsets(ref[1] - sourceScript.snapshot.getLength())) { - process(ref[0], linkedCodeOffset + sourceScript.snapshot.getLength()); + const mappingOffset = getMappingOffset(serviceScript, sourceScript); + for (const linkedCodeOffset of linkedCodeMap.getLinkedOffsets(ref[1] - mappingOffset)) { + process(ref[0], linkedCodeOffset + mappingOffset); } } } diff --git a/packages/typescript/lib/node/decorateLanguageServiceHost.ts b/packages/typescript/lib/node/decorateLanguageServiceHost.ts index dc149416..bd5db44d 100644 --- a/packages/typescript/lib/node/decorateLanguageServiceHost.ts +++ b/packages/typescript/lib/node/decorateLanguageServiceHost.ts @@ -14,7 +14,7 @@ export function decorateLanguageServiceHost( version: string, virtualScript?: { snapshot: ts.IScriptSnapshot; - kind: ts.ScriptKind; + scriptKind: ts.ScriptKind; extension: string; }, ]>(); @@ -94,7 +94,7 @@ export function decorateLanguageServiceHost( languageServiceHost.getScriptKind = fileName => { const virtualScript = updateVirtualScript(fileName); if (virtualScript) { - return virtualScript.kind; + return virtualScript.scriptKind; } return getScriptKind(fileName); }; @@ -123,14 +123,23 @@ export function decorateLanguageServiceHost( if (sourceScript?.generated) { const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root); if (serviceScript) { - const sourceContents = sourceScript.snapshot.getText(0, sourceScript.snapshot.getLength()); - let virtualContents = sourceContents.split('\n').map(line => ' '.repeat(line.length)).join('\n'); - virtualContents += serviceScript.code.snapshot.getText(0, serviceScript.code.snapshot.getLength()); - script[1] = { - extension: serviceScript.extension, - kind: serviceScript.scriptKind, - snapshot: ts.ScriptSnapshot.fromString(virtualContents), - }; + if (serviceScript.preventLeadingOffset) { + script[1] = { + extension: serviceScript.extension, + scriptKind: serviceScript.scriptKind, + snapshot: serviceScript.code.snapshot, + }; + } + else { + const sourceContents = sourceScript.snapshot.getText(0, sourceScript.snapshot.getLength()); + const virtualContents = sourceContents.split('\n').map(line => ' '.repeat(line.length)).join('\n') + + serviceScript.code.snapshot.getText(0, serviceScript.code.snapshot.getLength()); + script[1] = { + extension: serviceScript.extension, + scriptKind: serviceScript.scriptKind, + snapshot: ts.ScriptSnapshot.fromString(virtualContents), + }; + } } if (sourceScript.generated.languagePlugin.typescript?.getExtraServiceScripts) { console.warn('getExtraServiceScripts() is not available in TS plugin.'); diff --git a/packages/typescript/lib/node/proxyCreateProgram.ts b/packages/typescript/lib/node/proxyCreateProgram.ts index 1fb92b91..9adbdd79 100644 --- a/packages/typescript/lib/node/proxyCreateProgram.ts +++ b/packages/typescript/lib/node/proxyCreateProgram.ts @@ -35,7 +35,7 @@ export function proxyCreateProgram( getLanguagePlugins: (ts: typeof import('typescript'), options: ts.CreateProgramOptions) => LanguagePlugin[] ) { const sourceFileSnapshots = new FileMap<[ts.SourceFile | undefined, ts.IScriptSnapshot | undefined]>(ts.sys.useCaseSensitiveFileNames); - const parsedSourceFiles = new WeakMap(); + const parsedSourceFiles = new WeakMap(); let lastOptions: ts.CreateProgramOptions | undefined; let languagePlugins: LanguagePlugin[] | undefined; @@ -135,11 +135,11 @@ export function proxyCreateProgram( if (!parsedSourceFiles.has(originalSourceFile)) { const sourceScript = language!.scripts.get(fileName); assert(!!sourceScript, '!!sourceScript'); - parsedSourceFiles.set(originalSourceFile, originalSourceFile); + parsedSourceFiles.set(originalSourceFile, undefined); if (sourceScript.generated?.languagePlugin.typescript) { const { getServiceScript, getExtraServiceScripts } = sourceScript.generated.languagePlugin.typescript; const serviceScript = getServiceScript(sourceScript.generated.root); - if (serviceScript) { + if (serviceScript && !serviceScript.preventLeadingOffset) { let patchedText = originalSourceFile.text.split('\n').map(line => ' '.repeat(line.length)).join('\n'); let scriptKind = ts.ScriptKind.TS; scriptKind = serviceScript.scriptKind; @@ -160,7 +160,7 @@ export function proxyCreateProgram( } } } - return parsedSourceFiles.get(originalSourceFile); + return parsedSourceFiles.get(originalSourceFile) ?? originalSourceFile; }; if (extensions.length) { diff --git a/packages/typescript/lib/node/transform.ts b/packages/typescript/lib/node/transform.ts index 0ca7440a..5347410b 100644 --- a/packages/typescript/lib/node/transform.ts +++ b/packages/typescript/lib/node/transform.ts @@ -1,4 +1,4 @@ -import type { CodeInformation, SourceMap, SourceScript } from '@volar/language-core'; +import type { CodeInformation, SourceMap, SourceScript, TypeScriptServiceScript } from '@volar/language-core'; import { Language, shouldReportDiagnostics } from '@volar/language-core'; import type * as ts from 'typescript'; import { getServiceScript, notEmpty } from './utils'; @@ -34,7 +34,7 @@ export function transformDiagnostic(language: Language< ) { const [serviceScript, sourceScript, map] = getServiceScript(language, diagnostic.file.fileName); if (serviceScript) { - const sourceSpan = transformTextSpan(sourceScript, map, { start: diagnostic.start, length: diagnostic.length }, shouldReportDiagnostics); + const sourceSpan = transformTextSpan(serviceScript, sourceScript, map, { start: diagnostic.start, length: diagnostic.length }, shouldReportDiagnostics); if (sourceSpan) { if (isTsc) { fillSourceFileText(language, diagnostic.file); @@ -64,7 +64,7 @@ export function fillSourceFileText(language: Language, sourceFile: ts.So } transformedSourceFile.add(sourceFile); const [serviceScript, sourceScript] = getServiceScript(language, sourceFile.fileName); - if (serviceScript) { + if (serviceScript && !serviceScript.preventLeadingOffset) { sourceFile.text = sourceScript.snapshot.getText(0, sourceScript.snapshot.getLength()) + sourceFile.text.substring(sourceScript.snapshot.getLength()); } @@ -116,16 +116,21 @@ export function transformDocumentSpan(language: Langu }; } -export function transformSpan(language: Language, fileName: string | undefined, textSpan: ts.TextSpan | undefined, filter: (data: CodeInformation) => boolean): { +export function transformSpan( + language: Language, + fileName: string | undefined, + textSpan: ts.TextSpan | undefined, + filter: (data: CodeInformation) => boolean +): { fileName: string; textSpan: ts.TextSpan; } | undefined { if (!fileName || !textSpan) { return; } - const [virtualFile, sourceScript, map] = getServiceScript(language, fileName); - if (virtualFile) { - const sourceSpan = transformTextSpan(sourceScript, map, textSpan, filter); + const [serviceScript, sourceScript, map] = getServiceScript(language, fileName); + if (serviceScript) { + const sourceSpan = transformTextSpan(serviceScript, sourceScript, map, textSpan, filter); if (sourceSpan) { return { fileName, @@ -142,12 +147,13 @@ export function transformSpan(language: Language, fileName: string | und } export function transformTextChange( + serviceScript: TypeScriptServiceScript, sourceScript: SourceScript, map: SourceMap, textChange: ts.TextChange, filter: (data: CodeInformation) => boolean ): ts.TextChange | undefined { - const sourceSpan = transformTextSpan(sourceScript, map, textChange.span, filter); + const sourceSpan = transformTextSpan(serviceScript, sourceScript, map, textChange.span, filter); if (sourceSpan) { return { newText: textChange.newText, @@ -157,6 +163,7 @@ export function transformTextChange( } export function transformTextSpan( + serviceScript: TypeScriptServiceScript, sourceScript: SourceScript, map: SourceMap, textSpan: ts.TextSpan, @@ -164,8 +171,8 @@ export function transformTextSpan( ): ts.TextSpan | undefined { const start = textSpan.start; const end = textSpan.start + textSpan.length; - const sourceStart = toSourceOffset(sourceScript, map, start, filter); - const sourceEnd = toSourceOffset(sourceScript, map, end, filter); + const sourceStart = toSourceOffset(serviceScript, sourceScript, map, start, filter); + const sourceEnd = toSourceOffset(serviceScript, sourceScript, map, end, filter); if (sourceStart !== undefined && sourceEnd !== undefined && sourceEnd >= sourceStart) { return { start: sourceStart, @@ -174,24 +181,47 @@ export function transformTextSpan( } } -export function toSourceOffset(sourceScript: SourceScript, map: SourceMap, position: number, filter: (data: CodeInformation) => boolean) { - for (const [sourceOffset, mapping] of map.getSourceOffsets(position - sourceScript.snapshot.getLength())) { +export function toSourceOffset( + serviceScript: TypeScriptServiceScript, + sourceScript: SourceScript, + map: SourceMap, + position: number, + filter: (data: CodeInformation) => boolean +) { + for (const [sourceOffset, mapping] of map.getSourceOffsets(position - getMappingOffset(serviceScript, sourceScript))) { if (filter(mapping.data)) { return sourceOffset; } } } -export function toGeneratedOffset(sourceScript: SourceScript, map: SourceMap, position: number, filter: (data: CodeInformation) => boolean) { +export function toGeneratedOffset( + serviceScript: TypeScriptServiceScript, + sourceScript: SourceScript, + map: SourceMap, + position: number, + filter: (data: CodeInformation) => boolean +) { for (const [generateOffset, mapping] of map.getGeneratedOffsets(position)) { if (filter(mapping.data)) { - return generateOffset + sourceScript.snapshot.getLength(); + return generateOffset + getMappingOffset(serviceScript, sourceScript); } } } -export function* toGeneratedOffsets(sourceScript: SourceScript, map: SourceMap, position: number) { +export function* toGeneratedOffsets( + serviceScript: TypeScriptServiceScript, + sourceScript: SourceScript, + map: SourceMap, + position: number +) { for (const [generateOffset, mapping] of map.getGeneratedOffsets(position)) { - yield [generateOffset + sourceScript.snapshot.getLength(), mapping] as const; + yield [generateOffset + getMappingOffset(serviceScript, sourceScript), mapping] as const; } } + +export function getMappingOffset(serviceScript: TypeScriptServiceScript, sourceScript: SourceScript) { + return !serviceScript.preventLeadingOffset + ? sourceScript.snapshot.getLength() + : 0; +}