@vuedx/typescript-plugin-vue
Advanced tools
Comparing version 0.1.4 to 0.1.5
import TS from 'typescript/lib/tsserverlibrary'; | ||
interface Modules { | ||
typescript: TS; | ||
typescript: typeof TS; | ||
} | ||
@@ -6,0 +6,0 @@ |
@@ -10,2 +10,4 @@ 'use strict'; | ||
var vscodeUri = require('vscode-uri'); | ||
var analyze = require('@vuedx/analyze'); | ||
var templateAstTypes = require('@vuedx/template-ast-types'); | ||
var QuickLRU = _interopDefault(require('quick-lru')); | ||
@@ -164,3 +166,3 @@ | ||
// Enable .vue after enhancing the language server. | ||
setTimeout(() => context.projectService.setHostConfiguration({ extraFileExtensions: [] }), 0); | ||
context.projectService.setHostConfiguration({ extraFileExtensions: [] }); | ||
} | ||
@@ -331,2 +333,9 @@ function patchLanguageServiceHost(context, languageServiceHost) { | ||
} | ||
function computeIdentifierReplacement(source, identifer) { | ||
const RE = new RegExp(`\\b${identifer}\\b`); | ||
const match = RE.exec(source); | ||
if (!match) | ||
return { prefixText: source, suffixText: '' }; | ||
return { prefixText: source.substr(0, match.index), suffixText: source.substr(match.index + match[0].length) }; | ||
} | ||
@@ -517,2 +526,36 @@ const noop = { | ||
function findTemplateNodeAt(ast, position) { | ||
const found = { | ||
node: null, | ||
ancestors: [], | ||
}; | ||
templateAstTypes.traverseDepth(ast, (node, ancestors) => { | ||
if (node.loc.start.offset <= position && position <= node.loc.end.offset) { | ||
found.node = node; | ||
found.ancestors = ancestors; | ||
return true; | ||
} | ||
return false; | ||
}); | ||
if (found.node) { | ||
console.log('FOUND NODE in ' + found.node.loc.source + ` at ${position} of (${found.node.loc.start.offset}, ${found.node.loc.end.offset})`); | ||
} | ||
else { | ||
console.log('NOT FOUND'); | ||
} | ||
return found; | ||
} | ||
function createCachedAnalyzer() { | ||
const cache = new QuickLRU({ maxSize: 1000 }); | ||
const analyzer = analyze.createFullAnalyzer([], { babel: { plugins: ['typescript', 'jsx'] } }); | ||
return (document) => { | ||
const key = `${document.version}::${document.fsPath}`; | ||
if (cache.has(key)) | ||
return cache.get(key); | ||
const info = analyzer.analyze(document.getText(), document.fsPath); | ||
cache.set(key, info); | ||
return info; | ||
}; | ||
} | ||
function createTemplateLanguageServer({ helpers: h, service, context, }) { | ||
@@ -522,5 +565,2 @@ function getRenderDoc(fileName) { | ||
} | ||
function getTemplateDoc(fileName) { | ||
return getRenderDoc(fileName).container.getDocument('template'); | ||
} | ||
function getTextSpan(document, span) { | ||
@@ -535,2 +575,3 @@ if (h.isRenderFunctionDocument(document)) { | ||
const cache = new QuickLRU({ maxSize: 1000 }); | ||
const getComponentInfo = createCachedAnalyzer(); | ||
return { | ||
@@ -628,24 +669,52 @@ ...noop, | ||
const document = getRenderDoc(fileName); | ||
const template = getTemplateDoc(fileName); | ||
const offset = (_a = document.getGeneratedOffsetAt(position)) === null || _a === void 0 ? void 0 : _a.offset; | ||
if (vueVirtualTextdocument.isNumber(offset)) { | ||
const result = service.getRenameInfo(fileName, offset, options); | ||
if (result.canRename) { | ||
result.triggerSpan = getTextSpan(document, result.triggerSpan); | ||
const prefix = template.getText(result.triggerSpan.start - 1, 1); | ||
if (result.displayName === '$event') { | ||
return { | ||
canRename: false, | ||
localizedErrorMessage: '$event is builtin variable, it cannot be renamed.', | ||
}; | ||
const { node, ancestors } = findTemplateNodeAt(document.ast, position); | ||
if (templateAstTypes.isSimpleExpressionNode(node)) { | ||
const offset = (_a = document.getGeneratedOffsetAt(position)) === null || _a === void 0 ? void 0 : _a.offset; | ||
if (vueVirtualTextdocument.isNumber(offset)) { | ||
const result = service.getRenameInfo(fileName, offset, options); | ||
if (result.canRename) { | ||
result.triggerSpan = getTextSpan(document, result.triggerSpan); | ||
if (result.displayName === '$event') { | ||
return { | ||
canRename: false, | ||
localizedErrorMessage: '$event is builtin variable, it cannot be renamed.', | ||
}; | ||
} | ||
} | ||
else if (prefix === ':' || prefix === '@') { | ||
return { | ||
canRename: false, | ||
localizedErrorMessage: (prefix === ':' ? 'Prop/attribute' : 'Event name') + ' renaming is not supported.', | ||
}; | ||
} | ||
return result; | ||
} | ||
return result; | ||
} | ||
else if (templateAstTypes.isComponentNode(node)) { | ||
const info = getComponentInfo(document.container); | ||
const tagName = node.tag; | ||
const component = info.components.find((component) => component.aliases.includes(tagName)); | ||
if (component) { | ||
// TODO: Resolve path with TS. | ||
return { | ||
canRename: true, | ||
displayName: tagName, | ||
fullDisplayName: tagName, | ||
kind: context.typescript.ScriptElementKind.unknown, | ||
kindModifiers: 'tagName', | ||
triggerSpan: { | ||
start: node.loc.start.offset + 1, | ||
length: node.tag.length, | ||
}, | ||
fileToRename: undefined, | ||
}; | ||
} | ||
} | ||
else if (templateAstTypes.isPlainElementNode(node)) { | ||
return { | ||
canRename: true, | ||
displayName: node.tag, | ||
fullDisplayName: node.tag, | ||
kind: context.typescript.ScriptElementKind.unknown, | ||
kindModifiers: 'tagName', | ||
triggerSpan: { | ||
start: node.loc.start.offset + 1, | ||
length: node.tag.length, | ||
}, | ||
}; | ||
} | ||
return { | ||
@@ -659,8 +728,114 @@ canRename: false, | ||
const document = getRenderDoc(fileName); | ||
const offset = (_a = document.getGeneratedOffsetAt(position)) === null || _a === void 0 ? void 0 : _a.offset; | ||
if (vueVirtualTextdocument.isNumber(offset)) { | ||
return service.findRenameLocations(fileName, offset, findInStrings, findInComments); | ||
const { node, ancestors } = findTemplateNodeAt(document.ast, position); | ||
const edits = []; | ||
if (!node) { | ||
// TODO: Handle renames in script block. | ||
return; | ||
} | ||
return undefined; | ||
if (templateAstTypes.isSimpleExpressionNode(node)) { | ||
const offset = (_a = document.getGeneratedOffsetAt(position)) === null || _a === void 0 ? void 0 : _a.offset; | ||
if (vueVirtualTextdocument.isNumber(offset)) { | ||
return service.findRenameLocations(fileName, offset, findInStrings, findInComments); | ||
} | ||
} | ||
if (templateAstTypes.isPlainElementNode(node)) { | ||
edits.push({ | ||
fileName: document.container.fsPath, | ||
textSpan: { start: node.loc.start.offset + 1, length: node.tag.length }, | ||
}); | ||
if (!node.isSelfClosing) { | ||
edits.push({ | ||
fileName: document.container.fsPath, | ||
textSpan: { | ||
start: node.loc.start.offset + node.loc.source.lastIndexOf('</' + node.tag) + 2, | ||
length: node.tag.length, | ||
}, | ||
}); | ||
} | ||
} | ||
else if (templateAstTypes.isComponentNode(node)) { | ||
const info = getComponentInfo(document.container); | ||
const tagName = node.tag; | ||
const component = info.components.find((component) => component.aliases.includes(tagName)); | ||
if (component) { | ||
const importText = component.source.loc.source; | ||
// local name in import statement | ||
const importReplacement = computeIdentifierReplacement(importText, component.source.localName); | ||
const scriptFileName = document.container.getDocumentFileName('script'); | ||
let otherEdits = []; | ||
// find edit for local name except in import statement. | ||
if (scriptFileName) { | ||
const { prefixText } = computeIdentifierReplacement(component.loc.source, component.source.localName); | ||
const fromScript = service.findRenameLocations(scriptFileName, component.loc.start.offset + prefixText.length, findInStrings, findInComments); | ||
const start = component.source.loc.start.offset; | ||
const end = component.source.loc.end.offset; | ||
const vueFileName = document.container.fsPath; | ||
if (fromScript) { | ||
otherEdits.push(...fromScript.filter((edit) => vueVirtualTextdocument.getContainingFile(edit.fileName) === vueFileName && | ||
!edit.fileName.endsWith('_render.tsx') && | ||
(edit.textSpan.start < start || end < edit.textSpan.start))); | ||
} | ||
} | ||
if (component.source.exportName && !importReplacement.prefixText.trim().endsWith(' as')) { | ||
importReplacement.prefixText = importReplacement.prefixText + component.source.exportName + ' as '; | ||
} | ||
edits.push({ | ||
fileName: document.container.fsPath, | ||
textSpan: { start: component.source.loc.start.offset, length: importText.length }, | ||
...importReplacement, | ||
}); | ||
// if component is registered with an alias. | ||
if (component.source.localName !== component.name) { | ||
edits.push({ | ||
fileName: document.container.fsPath, | ||
textSpan: { start: component.loc.start.offset, length: component.loc.source.length }, | ||
...computeIdentifierReplacement(component.loc.source, component.name), | ||
}); | ||
} | ||
// other components using same import | ||
info.components | ||
.filter((current) => current.name !== component.name && | ||
current.source.moduleName === component.source.moduleName && | ||
current.source.exportName === component.source.exportName && | ||
current.source.localName === component.source.localName) | ||
.forEach((component) => { | ||
if (component.source.localName === component.name) { | ||
const start = component.loc.start.offset; | ||
otherEdits = otherEdits.filter((edit) => edit.textSpan.start !== start); | ||
edits.push({ | ||
fileName: document.container.fsPath, | ||
textSpan: { start: component.loc.start.offset, length: component.loc.source.length }, | ||
prefixText: /[a-z$_][a-z0-9$_]+/i.test(component.name) | ||
? component.name + ': ' | ||
: `'${component.name}': `, | ||
}); | ||
} | ||
}); | ||
templateAstTypes.traverseFast(document.ast, (node) => { | ||
if (templateAstTypes.isComponentNode(node)) { | ||
if (component.aliases.includes(node.tag)) { | ||
edits.push({ | ||
fileName: document.container.fsPath, | ||
textSpan: { start: node.loc.start.offset + 1, length: node.tag.length }, | ||
}); | ||
if (!node.isSelfClosing) { | ||
edits.push({ | ||
fileName: document.container.fsPath, | ||
textSpan: { | ||
start: node.loc.start.offset + node.loc.source.lastIndexOf('</' + node.tag) + 2, | ||
length: node.tag.length, | ||
}, | ||
}); | ||
} | ||
} | ||
} | ||
}); | ||
edits.push(...otherEdits); | ||
} | ||
} | ||
return edits; | ||
}, | ||
getEditsForFileRename(oldFilePath, newFilePath, formatOptions, preferences) { | ||
return []; | ||
}, | ||
getApplicableRefactors(fileName, positionOrRange, preferences) { | ||
@@ -757,6 +932,21 @@ const document = getRenderDoc(fileName); | ||
findRenameLocations(fileName, position, findInStrings, findInComments) { | ||
const document = h.getDocumentAt(fileName, position); | ||
const document = h.getVueDocument(fileName); | ||
if (!document) | ||
return; | ||
return choose(document).findRenameLocations(document.fsPath, position, findInStrings, findInComments); | ||
const block = document.blockAt(position); | ||
if (!block) | ||
return; | ||
const result = []; | ||
if (block.type === 'template' || block.type === 'script') { | ||
const fromTemplate = template.findRenameLocations(document.getDocumentFileName('_render'), position, findInStrings, findInComments); | ||
if (fromTemplate) | ||
result.push(...fromTemplate); | ||
} | ||
if (block.type === 'script') { | ||
const fromScript = template.findRenameLocations(document.getDocumentFileName('script'), position, findInStrings, findInComments); | ||
if (fromScript) | ||
result.push(...fromScript); | ||
} | ||
result.sort((a, b) => a.textSpan.start - b.textSpan.start); | ||
return result; | ||
}, | ||
@@ -990,3 +1180,3 @@ getApplicableRefactors(fileName, positionOrRange, preferences) { | ||
.findRenameLocations(fileName, position, findInStrings, findInComments)) === null || _a === void 0 ? void 0 : _a.map((item) => { | ||
options.context.log('xxx.findRenameLocations ' + JSON.stringify(item)); | ||
options.context.log('try.findRenameLocations ' + JSON.stringify(item)); | ||
if (vueVirtualTextdocument.isVirtualFile(item.fileName)) { | ||
@@ -1009,2 +1199,3 @@ item.originalContextSpan = item.contextSpan; | ||
} | ||
options.context.log('ok.findRenameLocations ' + JSON.stringify(item)); | ||
return item; | ||
@@ -1011,0 +1202,0 @@ }).filter(isNotNull); |
{ | ||
"name": "@vuedx/typescript-plugin-vue", | ||
"version": "0.1.4", | ||
"version": "0.1.5", | ||
"description": "TypeScript plugin for Vue", | ||
@@ -16,3 +16,6 @@ "main": "dist/typescript-plugin-vue.js", | ||
"dependencies": { | ||
"@vuedx/vue-virtual-textdocument": "0.1.4", | ||
"@vue/compiler-core": "^3.0.0-rc.5", | ||
"@vuedx/analyze": "^0.1.4", | ||
"@vuedx/template-ast-types": "0.1.5", | ||
"@vuedx/vue-virtual-textdocument": "0.1.5", | ||
"quick-lru": "^5.1.1", | ||
@@ -19,0 +22,0 @@ "typescript": "^3.9.7", |
Sorry, the diff of this file is not supported yet
166692
1286
7
+ Added@vuedx/analyze@^0.1.4
+ Added@nodelib/fs.scandir@2.1.5(transitive)
+ Added@nodelib/fs.stat@2.0.5(transitive)
+ Added@nodelib/fs.walk@1.2.8(transitive)
+ Added@vuedx/analyze@0.1.5(transitive)
+ Added@vuedx/compiler-tsx@0.1.5(transitive)
+ Added@vuedx/template-ast-types@0.1.5(transitive)
+ Added@vuedx/vue-virtual-textdocument@0.1.5(transitive)
+ Addedansi-regex@5.0.1(transitive)
+ Addedansi-styles@4.3.0(transitive)
+ Addedany-promise@1.3.0(transitive)
+ Addedbraces@3.0.3(transitive)
+ Addedchalk@4.1.2(transitive)
+ Addedcli-highlight@2.1.11(transitive)
+ Addedcliui@7.0.4(transitive)
+ Addedcolor-convert@2.0.1(transitive)
+ Addedcolor-name@1.1.4(transitive)
+ Addedcommander@6.2.1(transitive)
+ Addedemoji-regex@8.0.0(transitive)
+ Addedescalade@3.2.0(transitive)
+ Addedfast-glob@3.3.3(transitive)
+ Addedfastq@1.18.0(transitive)
+ Addedfill-range@7.1.1(transitive)
+ Addedget-caller-file@2.0.5(transitive)
+ Addedglob-parent@5.1.2(transitive)
+ Addedhas-flag@4.0.0(transitive)
+ Addedhash-sum@2.0.0(transitive)
+ Addedhighlight.js@10.7.3(transitive)
+ Addedis-extglob@2.1.1(transitive)
+ Addedis-fullwidth-code-point@3.0.0(transitive)
+ Addedis-glob@4.0.3(transitive)
+ Addedis-number@7.0.0(transitive)
+ Addedmerge2@1.4.1(transitive)
+ Addedmicromatch@4.0.8(transitive)
+ Addedmz@2.7.0(transitive)
+ Addedobject-assign@4.1.1(transitive)
+ Addedparse5@5.1.16.0.1(transitive)
+ Addedparse5-htmlparser2-tree-adapter@6.0.1(transitive)
+ Addedpicomatch@2.3.1(transitive)
+ Addedqueue-microtask@1.2.3(transitive)
+ Addedrequire-directory@2.1.1(transitive)
+ Addedreusify@1.0.4(transitive)
+ Addedrun-parallel@1.2.0(transitive)
+ Addedstring-width@4.2.3(transitive)
+ Addedstrip-ansi@6.0.1(transitive)
+ Addedsupports-color@7.2.0(transitive)
+ Addedthenify@3.3.1(transitive)
+ Addedthenify-all@1.6.0(transitive)
+ Addedto-regex-range@5.0.1(transitive)
+ Addedwrap-ansi@7.0.0(transitive)
+ Addedy18n@5.0.8(transitive)
+ Addedyargs@16.2.0(transitive)
+ Addedyargs-parser@20.2.9(transitive)
- Removed@vuedx/analyze@0.1.4(transitive)
- Removed@vuedx/compiler-tsx@0.1.4(transitive)
- Removed@vuedx/template-ast-types@0.1.4(transitive)
- Removed@vuedx/vue-virtual-textdocument@0.1.4(transitive)