-
-
Notifications
You must be signed in to change notification settings - Fork 198
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: map refactor/quick-fix of svelte files in typescript plugin (#2439)
- Loading branch information
1 parent
527c2ad
commit 4a9ef5e
Showing
3 changed files
with
182 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
178 changes: 178 additions & 0 deletions
178
packages/typescript-plugin/src/language-service/code-action.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
import type ts from 'typescript'; | ||
import { SvelteSnapshot, SvelteSnapshotManager } from '../svelte-snapshots'; | ||
import { isNotNullOrUndefined, isSvelteFilePath } from '../utils'; | ||
|
||
type _ts = typeof ts; | ||
|
||
export function decorateQuickFixAndRefactor( | ||
ls: ts.LanguageService, | ||
ts: _ts, | ||
snapshotManager: SvelteSnapshotManager | ||
) { | ||
const getEditsForRefactor = ls.getEditsForRefactor; | ||
const getCodeFixesAtPosition = ls.getCodeFixesAtPosition; | ||
|
||
ls.getEditsForRefactor = (...args) => { | ||
const result = getEditsForRefactor(...args); | ||
|
||
if (!result) { | ||
return; | ||
} | ||
|
||
const edits = result.edits.map(mapFileTextChanges).filter(isNotNullOrUndefined); | ||
if (edits.length === 0) { | ||
return; | ||
} | ||
|
||
return { | ||
...result, | ||
edits | ||
}; | ||
}; | ||
|
||
ls.getCodeFixesAtPosition = (...args) => { | ||
const result = getCodeFixesAtPosition(...args); | ||
|
||
return result | ||
.map((fix) => { | ||
return { | ||
...fix, | ||
changes: fix.changes.map(mapFileTextChanges).filter(isNotNullOrUndefined) | ||
}; | ||
}) | ||
.filter((fix) => fix.changes.length > 0); | ||
}; | ||
|
||
function mapFileTextChanges(change: ts.FileTextChanges) { | ||
const snapshot = snapshotManager.get(change.fileName); | ||
if (!isSvelteFilePath(change.fileName) || !snapshot) { | ||
return change; | ||
} | ||
|
||
let baseIndent: string | undefined; | ||
const getBaseIndent = () => { | ||
if (baseIndent !== undefined) { | ||
return baseIndent; | ||
} | ||
|
||
baseIndent = getIndentOfFirstStatement(ts, ls, change.fileName, snapshot); | ||
|
||
return baseIndent; | ||
}; | ||
|
||
const textChanges = change.textChanges | ||
.map((textChange) => mapEdit(textChange, snapshot, getBaseIndent)) | ||
.filter(isNotNullOrUndefined); | ||
|
||
// If part of the text changes are invalid, filter out the whole change | ||
if (textChanges.length === 0 || textChanges.length !== change.textChanges.length) { | ||
return null; | ||
} | ||
|
||
return { | ||
...change, | ||
textChanges | ||
}; | ||
} | ||
} | ||
|
||
function mapEdit(change: ts.TextChange, snapshot: SvelteSnapshot, getBaseIndent: () => string) { | ||
const isNewImportStatement = change.newText.trimStart().startsWith('import'); | ||
if (isNewImportStatement) { | ||
return mapNewImport(change, snapshot, getBaseIndent); | ||
} | ||
|
||
const span = snapshot.getOriginalTextSpan(change.span); | ||
|
||
if (!span) { | ||
return null; | ||
} | ||
|
||
return { | ||
span, | ||
newText: change.newText | ||
}; | ||
} | ||
|
||
function mapNewImport( | ||
change: ts.TextChange, | ||
snapshot: SvelteSnapshot, | ||
getBaseIndent: () => string | ||
): ts.TextChange | null { | ||
const previousLineEnds = getPreviousLineEnds(snapshot.getText(), change.span.start); | ||
|
||
if (previousLineEnds === -1) { | ||
return null; | ||
} | ||
const mappable = snapshot.getOriginalTextSpan({ | ||
start: previousLineEnds, | ||
length: 0 | ||
}); | ||
|
||
if (!mappable) { | ||
// There might not be any import at all but this is rare enough so ignore for now | ||
return null; | ||
} | ||
|
||
const originalText = snapshot.getOriginalText(); | ||
const span = { | ||
start: originalText.indexOf('\n', mappable.start) + 1, | ||
length: change.span.length | ||
}; | ||
|
||
const baseIndent = getBaseIndent(); | ||
let newText = baseIndent | ||
? change.newText | ||
.split('\n') | ||
.map((line) => (line ? baseIndent + line : line)) | ||
.join('\n') | ||
: change.newText; | ||
|
||
return { span, newText }; | ||
} | ||
|
||
function getPreviousLineEnds(text: string, start: number) { | ||
const index = text.lastIndexOf('\n', start); | ||
if (index === -1) { | ||
return index; | ||
} | ||
|
||
if (text[index - 1] === '\r') { | ||
return index - 1; | ||
} | ||
|
||
return index; | ||
} | ||
|
||
function getIndentOfFirstStatement( | ||
ts: _ts, | ||
ls: ts.LanguageService, | ||
fileName: string, | ||
snapshot: SvelteSnapshot | ||
) { | ||
const firstExportOrImport = ls | ||
.getProgram() | ||
?.getSourceFile(fileName) | ||
?.statements.find((node) => ts.isExportDeclaration(node) || ts.isImportDeclaration(node)); | ||
|
||
const originalPosition = firstExportOrImport | ||
? snapshot.getOriginalOffset(firstExportOrImport.getStart()) | ||
: -1; | ||
if (originalPosition === -1) { | ||
return ''; | ||
} | ||
|
||
const source = snapshot.getOriginalText(); | ||
const start = source.lastIndexOf('\n', originalPosition) + 1; | ||
let index = start; | ||
while (index < originalPosition) { | ||
const char = source[index]; | ||
if (char.trim()) { | ||
break; | ||
} | ||
|
||
index++; | ||
} | ||
|
||
return source.substring(start, index); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters