Skip to content

Commit

Permalink
feat: support extract component for options api
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Jul 26, 2023
1 parent 4de83b6 commit f012a57
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 55 deletions.
42 changes: 7 additions & 35 deletions packages/vue-language-service/src/languageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import createVueTemplateLanguageService from './plugins/vue-template';
import createVueTqService from './plugins/vue-twoslash-queries';
import createVisualizeHiddenCallbackParamService from './plugins/vue-visualize-hidden-callback-param';
import createDirectiveCommentsService from './plugins/vue-directive-comments';
import createExtractComponentService from './plugins/vue-extract-file';
import createExtractComponentService, { createAddComponentToOptionEdit } from './plugins/vue-extract-file';
import createToggleVBindService from './plugins/vue-toggle-v-bind-codeaction';
import { TagNameCasing, VueCompilerOptions } from './types';

Expand All @@ -48,8 +48,6 @@ export function resolveConfig(
return config;
}

const unicodeReg = /\\u/g;

function resolvePlugins(
services: Config['services'],
vueCompilerOptions: VueCompilerOptions,
Expand Down Expand Up @@ -167,41 +165,15 @@ function resolvePlugins(
const exportDefault = ast ? vue.scriptRanges.parseScriptRanges(ts, ast, false, true).exportDefault : undefined;
if (virtualFile && ast && exportDefault) {
const componentName = newName ?? item.textEdit.newText;
const textDoc = ctx.documents.getDocumentByFileName(virtualFile.snapshot, virtualFile.fileName);
// https://github.com/microsoft/TypeScript/issues/36174
const printer = ts.createPrinter();
if (exportDefault.componentsOption && exportDefault.componentsOptionNode) {
const newNode: typeof exportDefault.componentsOptionNode = {
...exportDefault.componentsOptionNode,
properties: [
...exportDefault.componentsOptionNode.properties,
ts.factory.createShorthandPropertyAssignment(componentName),
] as any as ts.NodeArray<ts.ObjectLiteralElementLike>,
};
const printText = printer.printNode(ts.EmitHint.Expression, newNode, ast);
item.additionalTextEdits.push({
range: {
start: textDoc.positionAt(exportDefault.componentsOption.start),
end: textDoc.positionAt(exportDefault.componentsOption.end),
},
newText: unescape(printText.replace(unicodeReg, '%u')),
});
}
else if (exportDefault.args && exportDefault.argsNode) {
const newNode: typeof exportDefault.argsNode = {
...exportDefault.argsNode,
properties: [
...exportDefault.argsNode.properties,
ts.factory.createShorthandPropertyAssignment(`components: { ${componentName} }`),
] as any as ts.NodeArray<ts.ObjectLiteralElementLike>,
};
const printText = printer.printNode(ts.EmitHint.Expression, newNode, ast);
const optionEdit = createAddComponentToOptionEdit(ts, ast, componentName);
if (optionEdit) {
const textDoc = ctx.documents.getDocumentByFileName(virtualFile.snapshot, virtualFile.fileName);
item.additionalTextEdits.push({
range: {
start: textDoc.positionAt(exportDefault.args.start),
end: textDoc.positionAt(exportDefault.args.end),
start: textDoc.positionAt(optionEdit.range.start),
end: textDoc.positionAt(optionEdit.range.end),
},
newText: unescape(printText.replace(unicodeReg, '%u')),
newText: optionEdit.newText,
});
}
}
Expand Down
96 changes: 76 additions & 20 deletions packages/vue-language-service/src/plugins/vue-extract-file.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { CreateFile, Service, ServiceContext, TextDocumentEdit, TextEdit } from '@volar/language-service';
import { ExpressionNode, type RootNode, type TemplateChildNode } from '@vue/compiler-dom';
import { SfcBlock, VueFile } from '@vue/language-core';
import { SfcBlock, VueFile, scriptRanges } from '@vue/language-core';
import type * as ts from 'typescript/lib/tsserverlibrary';
import type { Provide } from 'volar-service-typescript';
import type * as vscode from 'vscode-languageserver-protocol';

interface ActionData {
uri: string;
range: [number, number];
newName: string;
}

const unicodeReg = /\\u/g;

export default function (): Service {

return (ctx: ServiceContext<Provide> | undefined, modules): ReturnType<Service> => {
Expand Down Expand Up @@ -98,6 +101,39 @@ export default function (): Service {
newFileTags = newFileTags.reverse();
}

const currentFileEdits: vscode.TextEdit[] = [
{
range: {
start: document.positionAt(sfc.template.startTagEnd + templateCodeRange[0]),
end: document.positionAt(sfc.template.startTagEnd + templateCodeRange[1]),
},
newText: generateReplaceTemplate(),
},
{
range: lastImportNode ? {
start: document.positionAt(script.startTagEnd + lastImportNode.end),
end: document.positionAt(script.startTagEnd + lastImportNode.end),
} : {
start: document.positionAt(script.startTagEnd),
end: document.positionAt(script.startTagEnd),
},
newText: `\nimport ${newName} from './${newName}.vue'`,
},
];

if (sfc.script && sfc.scriptAst) {
const edit = createAddComponentToOptionEdit(ts, sfc.scriptAst, newName);
if (edit) {
currentFileEdits.push({
range: {
start: document.positionAt(sfc.script.startTagEnd + edit.range.start),
end: document.positionAt(sfc.script.startTagEnd + edit.range.end),
},
newText: edit.newText,
});
}
}

return {
...codeAction,
edit: {
Expand All @@ -108,25 +144,7 @@ export default function (): Service {
uri: document.uri,
version: null,
},
edits: [
{
range: {
start: document.positionAt(sfc.template.startTagEnd + templateCodeRange[0]),
end: document.positionAt(sfc.template.startTagEnd + templateCodeRange[1]),
},
newText: generateReplaceTemplate(),
} satisfies TextEdit,
{
range: lastImportNode ? {
start: document.positionAt(script.startTagEnd + lastImportNode.end),
end: document.positionAt(script.startTagEnd + lastImportNode.end),
} : {
start: document.positionAt(script.startTagEnd),
end: document.positionAt(script.startTagEnd),
},
newText: `\nimport ${newName} from './${newName}.vue'`,
} satisfies TextEdit,
],
edits: currentFileEdits,
} satisfies TextDocumentEdit,

// creating new file with content
Expand Down Expand Up @@ -298,3 +316,41 @@ function isInitialIndentNeeded(ts: typeof import("typescript/lib/tsserverlibrary
} as Record<ts.ScriptKind, string>;
return initialIndentSetting[languageKindIdMap[languageKind]] ?? false;
}

export function createAddComponentToOptionEdit(ts: typeof import('typescript/lib/tsserverlibrary'), ast: ts.SourceFile, componentName: string) {

const exportDefault = scriptRanges.parseScriptRanges(ts, ast, false, true).exportDefault;
if (!exportDefault)
return;

// https://github.com/microsoft/TypeScript/issues/36174
const printer = ts.createPrinter();
if (exportDefault.componentsOption && exportDefault.componentsOptionNode) {
const newNode: typeof exportDefault.componentsOptionNode = {
...exportDefault.componentsOptionNode,
properties: [
...exportDefault.componentsOptionNode.properties,
ts.factory.createShorthandPropertyAssignment(componentName),
] as any as ts.NodeArray<ts.ObjectLiteralElementLike>,
};
const printText = printer.printNode(ts.EmitHint.Expression, newNode, ast);
return {
range: exportDefault.componentsOption,
newText: unescape(printText.replace(unicodeReg, '%u')),
};
}
else if (exportDefault.args && exportDefault.argsNode) {
const newNode: typeof exportDefault.argsNode = {
...exportDefault.argsNode,
properties: [
...exportDefault.argsNode.properties,
ts.factory.createShorthandPropertyAssignment(`components: { ${componentName} }`),
] as any as ts.NodeArray<ts.ObjectLiteralElementLike>,
};
const printText = printer.printNode(ts.EmitHint.Expression, newNode, ast);
return {
range: exportDefault.args,
newText: unescape(printText.replace(unicodeReg, '%u')),
};
}
}

0 comments on commit f012a57

Please sign in to comment.