-
-
Notifications
You must be signed in to change notification settings - Fork 200
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(feat) extract component refactoring (#262)
* (feat) extract component refactoring #187 * (feat) extract component now with prompt for path/name - Is no longer part of the getCodeActions now, which might also improve performance a little * tests * docs * lint * (feat) update relative imports
- Loading branch information
1 parent
30e1c44
commit 6f30321
Showing
16 changed files
with
530 additions
and
64 deletions.
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
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
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
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
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
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
158 changes: 158 additions & 0 deletions
158
packages/language-server/src/plugins/svelte/features/getCodeActions/getRefactorings.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,158 @@ | ||
import * as path from 'path'; | ||
import { | ||
CreateFile, | ||
Position, | ||
Range, | ||
TextDocumentEdit, | ||
TextEdit, | ||
VersionedTextDocumentIdentifier, | ||
WorkspaceEdit, | ||
} from 'vscode-languageserver'; | ||
import { isRangeInTag, TagInformation, updateRelativeImport } from '../../../../lib/documents'; | ||
import { pathToUrl } from '../../../../utils'; | ||
import { SvelteDocument } from '../../SvelteDocument'; | ||
|
||
export interface ExtractComponentArgs { | ||
uri: string; | ||
range: Range; | ||
filePath: string; | ||
} | ||
|
||
export const extractComponentCommand = 'extract_to_svelte_component'; | ||
|
||
export async function executeRefactoringCommand( | ||
svelteDoc: SvelteDocument, | ||
command: string, | ||
args?: any[], | ||
): Promise<WorkspaceEdit | string | null> { | ||
if (command === extractComponentCommand && args) { | ||
return executeExtractComponentCommand(svelteDoc, args[1]); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
async function executeExtractComponentCommand( | ||
svelteDoc: SvelteDocument, | ||
refactorArgs: ExtractComponentArgs, | ||
): Promise<WorkspaceEdit | string | null> { | ||
const { range } = refactorArgs; | ||
|
||
if (isInvalidSelectionRange()) { | ||
return 'Invalid selection range'; | ||
} | ||
|
||
let filePath = refactorArgs.filePath || './NewComponent.svelte'; | ||
if (!filePath.endsWith('.svelte')) { | ||
filePath += '.svelte'; | ||
} | ||
if (!filePath.startsWith('.')) { | ||
filePath = './' + filePath; | ||
} | ||
const componentName = filePath.split('/').pop()?.split('.svelte')[0] || ''; | ||
const newFileUri = pathToUrl(path.join(path.dirname(svelteDoc.getFilePath()), filePath)); | ||
|
||
return <WorkspaceEdit>{ | ||
documentChanges: [ | ||
TextDocumentEdit.create(VersionedTextDocumentIdentifier.create(svelteDoc.uri, null), [ | ||
TextEdit.replace(range, `<${componentName}></${componentName}>`), | ||
createComponentImportTextEdit(), | ||
]), | ||
CreateFile.create(newFileUri, { overwrite: true }), | ||
createNewFileEdit(), | ||
], | ||
}; | ||
|
||
function isInvalidSelectionRange() { | ||
const text = svelteDoc.getText(); | ||
const offsetStart = svelteDoc.offsetAt(range.start); | ||
const offsetEnd = svelteDoc.offsetAt(range.end); | ||
const validStart = offsetStart === 0 || /[\s\W]/.test(text[offsetStart - 1]); | ||
const validEnd = offsetEnd === text.length - 1 || /[\s\W]/.test(text[offsetEnd]); | ||
return ( | ||
!validStart || | ||
!validEnd || | ||
isRangeInTag(range, svelteDoc.style) || | ||
isRangeInTag(range, svelteDoc.script) || | ||
isRangeInTag(range, svelteDoc.moduleScript) | ||
); | ||
} | ||
|
||
function createNewFileEdit() { | ||
const text = svelteDoc.getText(); | ||
const newText = [ | ||
getTemplate(), | ||
getTag(svelteDoc.script, false), | ||
getTag(svelteDoc.moduleScript, false), | ||
getTag(svelteDoc.style, true), | ||
] | ||
.filter((tag) => tag.start >= 0) | ||
.sort((a, b) => a.start - b.start) | ||
.map((tag) => tag.text) | ||
.join(''); | ||
|
||
return TextDocumentEdit.create(VersionedTextDocumentIdentifier.create(newFileUri, null), [ | ||
TextEdit.insert(Position.create(0, 0), newText), | ||
]); | ||
|
||
function getTemplate() { | ||
const startOffset = svelteDoc.offsetAt(range.start); | ||
return { | ||
text: text.substring(startOffset, svelteDoc.offsetAt(range.end)) + '\n\n', | ||
start: startOffset, | ||
}; | ||
} | ||
|
||
function getTag(tag: TagInformation | null, isStyleTag: boolean) { | ||
if (!tag) { | ||
return { text: '', start: -1 }; | ||
} | ||
|
||
const tagText = updateRelativeImports( | ||
svelteDoc, | ||
text.substring(tag.container.start, tag.container.end), | ||
filePath, | ||
isStyleTag, | ||
); | ||
return { | ||
text: `${tagText}\n\n`, | ||
start: tag.container.start, | ||
}; | ||
} | ||
} | ||
|
||
function createComponentImportTextEdit(): TextEdit { | ||
const startPos = (svelteDoc.script || svelteDoc.moduleScript)?.startPos; | ||
const importText = `\n import ${componentName} from '${filePath}';\n`; | ||
return TextEdit.insert( | ||
startPos || Position.create(0, 0), | ||
startPos ? importText : `<script>\n${importText}</script>`, | ||
); | ||
} | ||
} | ||
|
||
// `import {...} from '..'` or `import ... from '..'` | ||
// eslint-disable-next-line max-len | ||
const scriptRelativeImportRegex = /import\s+{[^}]*}.*['"`](((\.\/)|(\.\.\/)).*?)['"`]|import\s+\w+\s+from\s+['"`](((\.\/)|(\.\.\/)).*?)['"`]/g; | ||
This comment has been minimized.
Sorry, something went wrong. |
||
// `@import '..'` | ||
const styleRelativeImportRege = /@import\s+['"`](((\.\/)|(\.\.\/)).*?)['"`]/g; | ||
|
||
function updateRelativeImports( | ||
svelteDoc: SvelteDocument, | ||
tagText: string, | ||
newComponentRelativePath: string, | ||
isStyleTag: boolean, | ||
) { | ||
const oldPath = path.dirname(svelteDoc.getFilePath()); | ||
const newPath = path.dirname(path.join(oldPath, newComponentRelativePath)); | ||
const regex = isStyleTag ? styleRelativeImportRege : scriptRelativeImportRegex; | ||
let match = regex.exec(tagText); | ||
while (match) { | ||
// match[1]: match before | and style regex. match[5]: match after | (script regex) | ||
const importPath = match[1] || match[5]; | ||
const newImportPath = updateRelativeImport(oldPath, newPath, importPath); | ||
tagText = tagText.replace(importPath, newImportPath); | ||
match = regex.exec(tagText); | ||
} | ||
return tagText; | ||
} |
Oops, something went wrong.
ouch - writing this was prob a PITA