ts-gem-lsp 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +309 -81
- package/dist/bin.js.map +4 -4
- package/dist/cli.js +3558 -130
- package/dist/cli.js.map +4 -4
- package/dist/index.js +294 -79
- package/dist/index.js.map +4 -4
- package/package.json +3 -2
package/dist/bin.js
CHANGED
|
@@ -12883,6 +12883,19 @@ var defaultConfiguration = {
|
|
|
12883
12883
|
elementDefineRules: {}
|
|
12884
12884
|
};
|
|
12885
12885
|
|
|
12886
|
+
// ../gem/lib/server.js
|
|
12887
|
+
var { get, set, has, deleteProperty, defineProperty, getOwnPropertyDescriptor, ownKeys } = Reflect;
|
|
12888
|
+
var getRequestScope = globalThis.__GEM_SSR__;
|
|
12889
|
+
|
|
12890
|
+
// ../gem/lib/utils.js
|
|
12891
|
+
var { assign, fromEntries, entries, keys, defineProperty: defineProperty2, hasOwn } = Object;
|
|
12892
|
+
function camelToKebabCase(str) {
|
|
12893
|
+
return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
12894
|
+
}
|
|
12895
|
+
function kebabToCamelCase(str) {
|
|
12896
|
+
return str.replace(/-(.)/g, (_substr, $1) => $1.toUpperCase());
|
|
12897
|
+
}
|
|
12898
|
+
|
|
12886
12899
|
// ../../node_modules/.pnpm/@mantou+vscode-html-languageservice@5.3.6/node_modules/@mantou/vscode-html-languageservice/lib/esm/parser/htmlScanner.js
|
|
12887
12900
|
var l10n = __toESM(require_main2());
|
|
12888
12901
|
|
|
@@ -28857,15 +28870,6 @@ function getDefaultHTMLDataProvider() {
|
|
|
28857
28870
|
return newHTMLDataProvider("default", htmlData);
|
|
28858
28871
|
}
|
|
28859
28872
|
|
|
28860
|
-
// ../gem/lib/utils.js
|
|
28861
|
-
var { assign, fromEntries, entries, keys } = Object;
|
|
28862
|
-
function camelToKebabCase(str) {
|
|
28863
|
-
return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
28864
|
-
}
|
|
28865
|
-
function kebabToCamelCase(str) {
|
|
28866
|
-
return str.replace(/-(.)/g, (_substr, $1) => $1.toUpperCase());
|
|
28867
|
-
}
|
|
28868
|
-
|
|
28869
28873
|
// src/ts.ts
|
|
28870
28874
|
var ts;
|
|
28871
28875
|
async function loadTsModules(loader) {
|
|
@@ -28928,27 +28932,45 @@ function findElementRefs(file, rules) {
|
|
|
28928
28932
|
}
|
|
28929
28933
|
var ElementIndex = class {
|
|
28930
28934
|
#getRules;
|
|
28931
|
-
#
|
|
28935
|
+
#rules;
|
|
28936
|
+
#projects = /* @__PURE__ */ new Map();
|
|
28932
28937
|
constructor(getRules) {
|
|
28933
28938
|
this.#getRules = getRules;
|
|
28934
28939
|
}
|
|
28935
|
-
|
|
28936
|
-
|
|
28937
|
-
if (
|
|
28938
|
-
const
|
|
28939
|
-
current.refreshing ??= this.#scan(project).then((elements) => current.elements = elements).finally(() => current.refreshing = void 0);
|
|
28940
|
-
return current.elements ?? current.refreshing;
|
|
28940
|
+
/** 不传 URI 时全部重新扫描 */
|
|
28941
|
+
invalidate(uri) {
|
|
28942
|
+
if (uri === void 0) return this.#projects.clear();
|
|
28943
|
+
for (const state of this.#projects.values()) state.changed.add(uri);
|
|
28941
28944
|
}
|
|
28942
|
-
|
|
28943
|
-
const fileNames = (await project.program.getSourceFileNames()).filter((name) => !isLibFile(name));
|
|
28944
|
-
const files = await Promise.all(fileNames.map((name) => project.program.getSourceFile(name)));
|
|
28945
|
+
get(project) {
|
|
28945
28946
|
const rules = this.#getRules();
|
|
28946
|
-
|
|
28947
|
-
|
|
28948
|
-
|
|
28949
|
-
for (const ref of findElementRefs(file, rules)) elements.set(ref.tag, ref);
|
|
28947
|
+
if (rules !== this.#rules) {
|
|
28948
|
+
this.#rules = rules;
|
|
28949
|
+
this.#projects.clear();
|
|
28950
28950
|
}
|
|
28951
|
-
|
|
28951
|
+
let state = this.#projects.get(project.id);
|
|
28952
|
+
if (!state) {
|
|
28953
|
+
state = { files: /* @__PURE__ */ new Map(), elements: /* @__PURE__ */ new Map(), changed: /* @__PURE__ */ new Set(), queue: Promise.resolve() };
|
|
28954
|
+
this.#projects.set(project.id, state);
|
|
28955
|
+
}
|
|
28956
|
+
const result = state.queue.then(() => this.#update(project, state, rules));
|
|
28957
|
+
state.queue = result.catch(() => {
|
|
28958
|
+
});
|
|
28959
|
+
return result;
|
|
28960
|
+
}
|
|
28961
|
+
async #update(project, state, rules) {
|
|
28962
|
+
const changed = new Set([...state.changed].map((uri) => ts.api.documentURIToFileName(uri)));
|
|
28963
|
+
state.changed.clear();
|
|
28964
|
+
const fileNames = (await project.program.getSourceFileNames()).filter((name) => !isLibFile(name));
|
|
28965
|
+
const current = new Set(fileNames);
|
|
28966
|
+
const removed = [...state.files.keys()].filter((name) => !current.has(name));
|
|
28967
|
+
const stale = fileNames.filter((name) => !state.files.has(name) || changed.has(name));
|
|
28968
|
+
if (!removed.length && !stale.length) return state.elements;
|
|
28969
|
+
for (const name of removed) state.files.delete(name);
|
|
28970
|
+
const files = await Promise.all(stale.map((name) => project.program.getSourceFile(name)));
|
|
28971
|
+
stale.forEach((name, i) => state.files.set(name, files[i] ? findElementRefs(files[i], rules) : []));
|
|
28972
|
+
state.elements = new Map([...state.files.values()].flat().map((ref) => [ref.tag, ref]));
|
|
28973
|
+
return state.elements;
|
|
28952
28974
|
}
|
|
28953
28975
|
};
|
|
28954
28976
|
async function getUnionValues(type) {
|
|
@@ -29062,6 +29084,12 @@ function toPosition(text, offset) {
|
|
|
29062
29084
|
for (let i = text.indexOf("\n"); i !== -1 && i < lineStart; i = text.indexOf("\n", i + 1)) line++;
|
|
29063
29085
|
return { line, character: offset - lineStart };
|
|
29064
29086
|
}
|
|
29087
|
+
function getTouchingToken(file, offset) {
|
|
29088
|
+
const token = ts.ast.getTokenAtPosition(file, offset);
|
|
29089
|
+
if (offset === 0 || token.getStart(file) !== offset) return token;
|
|
29090
|
+
const previous = ts.ast.getTokenAtPosition(file, offset - 1);
|
|
29091
|
+
return previous.end === offset ? previous : token;
|
|
29092
|
+
}
|
|
29065
29093
|
function findAncestor(node, test) {
|
|
29066
29094
|
for (let n = node; n; n = n.parent) if (test(n)) return n;
|
|
29067
29095
|
}
|
|
@@ -29316,7 +29344,7 @@ function findClassNameAt(file, offset) {
|
|
|
29316
29344
|
const element2 = findAncestor(template.node, isElementClass);
|
|
29317
29345
|
return ref && element2 && { name: ref.name, range: ref.range, element: element2 };
|
|
29318
29346
|
}
|
|
29319
|
-
const token =
|
|
29347
|
+
const token = getTouchingToken(file, offset);
|
|
29320
29348
|
if (!ts.ast.isIdentifier(token) && !ts.ast.isStringLiteral(token)) return;
|
|
29321
29349
|
const isKey = getClassMapKeyNodes(file).includes(token);
|
|
29322
29350
|
const element = findAncestor(token, isElementClass);
|
|
@@ -74763,8 +74791,8 @@ var HtmlService = class {
|
|
|
74763
74791
|
if (!tag || startTagEnd === void 0 || vOffset > startTagEnd) return null;
|
|
74764
74792
|
const tagStart = node.start + 1;
|
|
74765
74793
|
if (vOffset <= tagStart + tag.length) {
|
|
74766
|
-
const
|
|
74767
|
-
const declaration2 = await
|
|
74794
|
+
const elementType = await resolveElementType(project, template.file, await this.#index.get(project), tag);
|
|
74795
|
+
const declaration2 = await elementType?.symbol?.declarations[0]?.resolve(project);
|
|
74768
74796
|
if (!declaration2) return null;
|
|
74769
74797
|
return [toLocationLink(template.toRangeFromOffsets(tagStart, tagStart + tag.length), declaration2)];
|
|
74770
74798
|
}
|
|
@@ -74787,11 +74815,32 @@ var HtmlService = class {
|
|
|
74787
74815
|
}
|
|
74788
74816
|
];
|
|
74789
74817
|
}
|
|
74818
|
+
const declaration = await this.#getPropDeclaration(project, template, tag, attrName);
|
|
74819
|
+
return declaration ? [toLocationLink(origin, declaration)] : null;
|
|
74820
|
+
}
|
|
74821
|
+
/** 光标所在的元素特性及其对应的属性声明 */
|
|
74822
|
+
async attributeAt(project, template, offset) {
|
|
74823
|
+
const vOffset = template.toVirtualOffset(offset);
|
|
74824
|
+
const node = this.#ls.parseHTMLDocument(template.doc).findNodeAt(vOffset);
|
|
74825
|
+
const { tag, startTagEnd } = node;
|
|
74826
|
+
if (!tag || startTagEnd === void 0 || vOffset > startTagEnd) return;
|
|
74827
|
+
const attrEntry = [...node.attributesMap].find(([, { start, end: end2 }]) => vOffset >= start && vOffset <= end2);
|
|
74828
|
+
if (!attrEntry) return;
|
|
74829
|
+
const [attrName, { end }] = attrEntry;
|
|
74830
|
+
const attr = getAttrName(attrName);
|
|
74831
|
+
const declaration = await this.#getPropDeclaration(project, template, tag, attrName);
|
|
74832
|
+
return declaration && {
|
|
74833
|
+
name: attr,
|
|
74834
|
+
/** `.prop` 使用属性名,其他使用特性名 */
|
|
74835
|
+
isProperty: attrName.startsWith("."),
|
|
74836
|
+
range: template.toRangeFromOffsets(end - attr.length, end),
|
|
74837
|
+
declaration
|
|
74838
|
+
};
|
|
74839
|
+
}
|
|
74840
|
+
async #getPropDeclaration(project, template, tag, attrName) {
|
|
74790
74841
|
const elementType = await resolveElementType(project, template.file, await this.#index.get(project), tag);
|
|
74791
|
-
const prop = await elementType?.type.getProperty(kebabToCamelCase(
|
|
74792
|
-
|
|
74793
|
-
if (!declaration) return null;
|
|
74794
|
-
return [toLocationLink(origin, declaration)];
|
|
74842
|
+
const prop = await elementType?.type.getProperty(kebabToCamelCase(getAttrName(attrName)));
|
|
74843
|
+
return prop?.declarations[0]?.resolve(project);
|
|
74795
74844
|
}
|
|
74796
74845
|
/** 匹配的开始和结束标签 */
|
|
74797
74846
|
highlights(template, offset) {
|
|
@@ -74820,6 +74869,9 @@ var HtmlService = class {
|
|
|
74820
74869
|
|
|
74821
74870
|
// src/tags.ts
|
|
74822
74871
|
var htmlLs3 = getLanguageService();
|
|
74872
|
+
var cssLs = getCSSLanguageService();
|
|
74873
|
+
var REGISTRY_METHODS = /* @__PURE__ */ new Set(["get", "whenDefined"]);
|
|
74874
|
+
var SELECTOR_METHODS = /* @__PURE__ */ new Set(["querySelector", "querySelectorAll", "closest", "matches"]);
|
|
74823
74875
|
function getTagRanges(template, node) {
|
|
74824
74876
|
const tag = node.tag;
|
|
74825
74877
|
const ranges = [template.toRangeFromOffsets(node.start + 1, node.start + 1 + tag.length)];
|
|
@@ -74849,10 +74901,45 @@ function getCustomElementArg(node) {
|
|
|
74849
74901
|
}
|
|
74850
74902
|
}
|
|
74851
74903
|
function findDefinedTag(file, offset) {
|
|
74852
|
-
const node =
|
|
74904
|
+
const node = getTouchingToken(file, offset);
|
|
74853
74905
|
if (!ts.ast.isStringLiteral(node) || !isCustomElementCall(node.parent)) return;
|
|
74854
74906
|
return { tag: node.text, range: stringContentRange2(file, node), isDefinition: true };
|
|
74855
74907
|
}
|
|
74908
|
+
function getSelectorTags(selector) {
|
|
74909
|
+
const result = [];
|
|
74910
|
+
const visit3 = (node) => {
|
|
74911
|
+
if (node.type === NodeType.ElementNameSelector)
|
|
74912
|
+
result.push({ tag: node.getText(), start: node.offset, end: node.end });
|
|
74913
|
+
node.getChildren().forEach(visit3);
|
|
74914
|
+
};
|
|
74915
|
+
visit3(cssLs.parseStylesheet(TextDocument2.create("gem-selector://", "css", 0, `${selector}{}`)));
|
|
74916
|
+
return result;
|
|
74917
|
+
}
|
|
74918
|
+
function getCallTags(node) {
|
|
74919
|
+
if (!ts.ast.isCallExpression(node) || !ts.ast.isPropertyAccessExpression(node.expression)) return;
|
|
74920
|
+
const [arg] = node.arguments;
|
|
74921
|
+
if (!arg || !(ts.ast.isStringLiteral(arg) || ts.ast.isNoSubstitutionTemplateLiteral(arg))) return;
|
|
74922
|
+
const { name, expression } = node.expression;
|
|
74923
|
+
const isRegistry = ts.ast.isIdentifier(expression) && expression.text === "customElements";
|
|
74924
|
+
if (name.text === "createElement" || isRegistry && REGISTRY_METHODS.has(name.text)) {
|
|
74925
|
+
return { arg, tags: [{ tag: arg.text, start: 0, end: arg.text.length }] };
|
|
74926
|
+
}
|
|
74927
|
+
if (SELECTOR_METHODS.has(name.text)) return { arg, tags: getSelectorTags(arg.text) };
|
|
74928
|
+
}
|
|
74929
|
+
function stringTagRange(file, arg, start, end) {
|
|
74930
|
+
const contentStart = arg.getStart(file) + 1;
|
|
74931
|
+
return { start: toPosition(file.text, contentStart + start), end: toPosition(file.text, contentStart + end) };
|
|
74932
|
+
}
|
|
74933
|
+
function findCallTag(file, offset) {
|
|
74934
|
+
const token = getTouchingToken(file, offset);
|
|
74935
|
+
const call = token.parent && getCallTags(token.parent);
|
|
74936
|
+
if (call?.arg !== token) return;
|
|
74937
|
+
const contentStart = token.getStart(file) + 1;
|
|
74938
|
+
const found = call.tags.find(({ start, end }) => offset >= contentStart + start && offset <= contentStart + end);
|
|
74939
|
+
if (!found) return;
|
|
74940
|
+
const range = stringTagRange(file, token, found.start, found.end);
|
|
74941
|
+
return { tag: found.tag, range, isDefinition: false, tagRanges: [range] };
|
|
74942
|
+
}
|
|
74856
74943
|
function findTemplateTag(file, offset) {
|
|
74857
74944
|
const template = findTemplate(file, offset);
|
|
74858
74945
|
if (template?.kind !== "html") return;
|
|
@@ -74868,7 +74955,7 @@ function findTemplateTag(file, offset) {
|
|
|
74868
74955
|
return { tag: node.tag, range: tagRanges[0], isDefinition: false, tagRanges };
|
|
74869
74956
|
}
|
|
74870
74957
|
function findTagAt(file, offset) {
|
|
74871
|
-
return findDefinedTag(file, offset) ?? findTemplateTag(file, offset);
|
|
74958
|
+
return findDefinedTag(file, offset) ?? findCallTag(file, offset) ?? findTemplateTag(file, offset);
|
|
74872
74959
|
}
|
|
74873
74960
|
async function getProjectFiles(project) {
|
|
74874
74961
|
const fileNames = (await project.program.getSourceFileNames()).filter((name) => !isDepFile(name));
|
|
@@ -74897,6 +74984,13 @@ async function findTagLocations(project, tag, css) {
|
|
|
74897
74984
|
const arg = ts.ast.isClassDeclaration(node) ? getCustomElementArg(node) : void 0;
|
|
74898
74985
|
if (arg?.text === tag) locations.push({ uri, range: stringContentRange2(file, arg) });
|
|
74899
74986
|
}
|
|
74987
|
+
file.forEachChild(function visit3(node) {
|
|
74988
|
+
const call = getCallTags(node);
|
|
74989
|
+
for (const found of call?.tags ?? []) {
|
|
74990
|
+
if (found.tag === tag) locations.push({ uri, range: stringTagRange(file, call.arg, found.start, found.end) });
|
|
74991
|
+
}
|
|
74992
|
+
node.forEachChild(visit3);
|
|
74993
|
+
});
|
|
74900
74994
|
}
|
|
74901
74995
|
return locations;
|
|
74902
74996
|
}
|
|
@@ -74908,73 +75002,142 @@ function toTextEdits(locations, newText) {
|
|
|
74908
75002
|
|
|
74909
75003
|
// src/props.ts
|
|
74910
75004
|
var htmlLs4 = getLanguageService();
|
|
74911
|
-
var ATTRIBUTE_DECORATORS = ["attribute", "numattribute", "boolattribute"
|
|
75005
|
+
var ATTRIBUTE_DECORATORS = ["attribute", "numattribute", "boolattribute"];
|
|
75006
|
+
var PROPERTY_DECORATORS = ["property"];
|
|
74912
75007
|
var EMITTER_DECORATORS = ["emitter", "globalemitter"];
|
|
74913
|
-
|
|
74914
|
-
|
|
74915
|
-
|
|
74916
|
-
|
|
74917
|
-
|
|
74918
|
-
|
|
74919
|
-
|
|
74920
|
-
|
|
74921
|
-
|
|
74922
|
-
|
|
74923
|
-
|
|
75008
|
+
var ATTRIBUTE_METHODS = /* @__PURE__ */ new Set([
|
|
75009
|
+
"getAttribute",
|
|
75010
|
+
"setAttribute",
|
|
75011
|
+
"hasAttribute",
|
|
75012
|
+
"removeAttribute",
|
|
75013
|
+
"toggleAttribute"
|
|
75014
|
+
]);
|
|
75015
|
+
var EVENT_METHODS = /* @__PURE__ */ new Set(["addEventListener", "removeEventListener"]);
|
|
75016
|
+
var asKebab = (name) => camelToKebabCase(name);
|
|
75017
|
+
var asCamel = (name) => name;
|
|
75018
|
+
function getPropKind(decorators) {
|
|
75019
|
+
if (decorators.some((d) => EMITTER_DECORATORS.includes(d))) return "event";
|
|
75020
|
+
if (decorators.some((d) => ATTRIBUTE_DECORATORS.includes(d))) return "attribute";
|
|
75021
|
+
if (decorators.some((d) => PROPERTY_DECORATORS.includes(d))) return "property";
|
|
75022
|
+
}
|
|
75023
|
+
function getAttrForms({ name, kind }) {
|
|
75024
|
+
const kebab = camelToKebabCase(name.text);
|
|
75025
|
+
if (kind === "event") return [["@", kebab, asKebab]];
|
|
75026
|
+
return [
|
|
75027
|
+
["", kebab, asKebab],
|
|
75028
|
+
["?", kebab, asKebab],
|
|
75029
|
+
[".", name.text, asCamel]
|
|
75030
|
+
];
|
|
74924
75031
|
}
|
|
74925
75032
|
function findElementProp(file, offset) {
|
|
74926
|
-
const token =
|
|
75033
|
+
const token = getTouchingToken(file, offset);
|
|
74927
75034
|
const decl = token.parent;
|
|
74928
75035
|
if (!ts.ast.isIdentifier(token) || !decl || !ts.ast.isPropertyDeclaration(decl) || decl.name !== token) return;
|
|
74929
75036
|
const element = decl.parent;
|
|
74930
75037
|
if (!ts.ast.isClassDeclaration(element) || !getTagFromDecorator(element)) return;
|
|
74931
|
-
const
|
|
74932
|
-
return
|
|
75038
|
+
const kind = getPropKind(getDecoratorNames(decl));
|
|
75039
|
+
return kind && { name: token, element, kind };
|
|
74933
75040
|
}
|
|
74934
|
-
async function
|
|
74935
|
-
const
|
|
74936
|
-
const
|
|
74937
|
-
const
|
|
74938
|
-
|
|
74939
|
-
|
|
74940
|
-
|
|
74941
|
-
|
|
74942
|
-
return type && await checker.isTypeAssignableTo(type.type, elementType) ? tag : void 0;
|
|
74943
|
-
})
|
|
74944
|
-
);
|
|
74945
|
-
return new Set(tags.filter((tag) => tag !== void 0));
|
|
75041
|
+
async function getRelatedPropTest(project, prop) {
|
|
75042
|
+
const file = prop.name.getSourceFile();
|
|
75043
|
+
const entries2 = await project.languageService.getReferencedSymbolsForNode(prop.name, prop.name.getStart(file));
|
|
75044
|
+
const declarations = new Set(entries2.flatMap(({ symbol }) => symbol?.declarations.map(declarationKey) ?? []));
|
|
75045
|
+
return async (type) => {
|
|
75046
|
+
const symbol = await type.getProperty(prop.name.text);
|
|
75047
|
+
return !!symbol?.declarations.some((handle) => declarations.has(declarationKey(handle)));
|
|
75048
|
+
};
|
|
74946
75049
|
}
|
|
75050
|
+
var declarationKey = ({ path: path2, index }) => `${path2}:${index}`;
|
|
74947
75051
|
function forEachHtmlNode3(nodes, fn) {
|
|
74948
75052
|
for (const node of nodes) {
|
|
74949
75053
|
fn(node);
|
|
74950
75054
|
forEachHtmlNode3(node.children, fn);
|
|
74951
75055
|
}
|
|
74952
75056
|
}
|
|
74953
|
-
|
|
74954
|
-
|
|
75057
|
+
function forEachNode(root, fn) {
|
|
75058
|
+
root.forEachChild(function visit3(node) {
|
|
75059
|
+
fn(node);
|
|
75060
|
+
node.forEachChild(visit3);
|
|
75061
|
+
});
|
|
75062
|
+
}
|
|
75063
|
+
function isStringArg(node) {
|
|
75064
|
+
return !!node && (ts.ast.isStringLiteral(node) || ts.ast.isNoSubstitutionTemplateLiteral(node));
|
|
75065
|
+
}
|
|
75066
|
+
function nodeRange2(file, node, quote = 0) {
|
|
75067
|
+
const start = node.getStart(file) + quote;
|
|
75068
|
+
return { start: toPosition(file.text, start), end: toPosition(file.text, node.end - quote) };
|
|
75069
|
+
}
|
|
75070
|
+
function forEachSpreadObject(template, fn) {
|
|
75071
|
+
if (!ts.ast.isTemplateExpression(template.node)) return;
|
|
75072
|
+
const html = htmlLs4.parseHTMLDocument(template.doc);
|
|
75073
|
+
const text = template.doc.getText();
|
|
75074
|
+
for (const { expression } of template.node.templateSpans) {
|
|
75075
|
+
if (!ts.ast.isObjectLiteralExpression(expression)) continue;
|
|
75076
|
+
const vOffset = template.toVirtualOffset(expression.getStart(template.file)) - 2;
|
|
75077
|
+
const node = html.findNodeAt(vOffset);
|
|
75078
|
+
if (!node.tag || node.startTagEnd === void 0 || vOffset > node.startTagEnd) continue;
|
|
75079
|
+
if (/=\s*["']?\s*$/.test(text.slice(node.start, vOffset))) continue;
|
|
75080
|
+
fn(node.tag, expression);
|
|
75081
|
+
}
|
|
75082
|
+
}
|
|
75083
|
+
async function findPropReferences(project, prop, index) {
|
|
75084
|
+
const isRelated = await getRelatedPropTest(project, prop);
|
|
75085
|
+
const elements = await index.get(project);
|
|
75086
|
+
const elementFile = prop.element.getSourceFile();
|
|
75087
|
+
const tags = /* @__PURE__ */ new Set();
|
|
75088
|
+
await Promise.all(
|
|
75089
|
+
[...elements.keys()].map(async (tag) => {
|
|
75090
|
+
const type = await resolveElementType(project, elementFile, elements, tag);
|
|
75091
|
+
if (type && await isRelated(type.type)) tags.add(tag);
|
|
75092
|
+
})
|
|
75093
|
+
);
|
|
75094
|
+
const name = prop.name.text;
|
|
75095
|
+
const kebab = camelToKebabCase(name);
|
|
75096
|
+
const forms = getAttrForms(prop);
|
|
75097
|
+
const methods = prop.kind === "event" ? EVENT_METHODS : prop.kind === "attribute" ? ATTRIBUTE_METHODS : void 0;
|
|
74955
75098
|
const result = [];
|
|
75099
|
+
const calls = [];
|
|
74956
75100
|
for (const file of await getProjectFiles(project)) {
|
|
74957
75101
|
const uri = ts.api.fileNameToDocumentURI(file.fileName);
|
|
74958
75102
|
for (const template of findTemplates(file, "html")) {
|
|
74959
75103
|
forEachHtmlNode3(htmlLs4.parseHTMLDocument(template.doc).roots, (node) => {
|
|
74960
75104
|
if (!node.tag || !tags.has(node.tag)) return;
|
|
74961
|
-
for (const
|
|
74962
|
-
const
|
|
74963
|
-
const info = node.attributesMap.get(decorate + name);
|
|
75105
|
+
for (const [decorate, attr, newText] of forms) {
|
|
75106
|
+
const info = node.attributesMap.get(decorate + attr);
|
|
74964
75107
|
if (!info) continue;
|
|
74965
|
-
result.push({ uri,
|
|
75108
|
+
result.push({ uri, range: template.toRangeFromOffsets(info.start + decorate.length, info.end), newText });
|
|
75109
|
+
}
|
|
75110
|
+
});
|
|
75111
|
+
if (prop.kind === "event") continue;
|
|
75112
|
+
forEachSpreadObject(template, (tag, obj) => {
|
|
75113
|
+
if (!tags.has(tag)) return;
|
|
75114
|
+
for (const p of obj.properties) {
|
|
75115
|
+
const key = ts.ast.isSpreadAssignment(p) ? void 0 : p.name;
|
|
75116
|
+
if (!key || !(ts.ast.isIdentifier(key) || ts.ast.isStringLiteral(key)) || key.text !== name) continue;
|
|
75117
|
+
const newText = ts.ast.isShorthandPropertyAssignment(p) ? (n) => `${n}: ${name}` : asCamel;
|
|
75118
|
+
result.push({ uri, range: nodeRange2(file, key, ts.ast.isStringLiteral(key) ? 1 : 0), newText });
|
|
74966
75119
|
}
|
|
74967
75120
|
});
|
|
74968
75121
|
}
|
|
75122
|
+
if (!methods) continue;
|
|
75123
|
+
forEachNode(file, (node) => {
|
|
75124
|
+
if (!ts.ast.isCallExpression(node) || !ts.ast.isPropertyAccessExpression(node.expression)) return;
|
|
75125
|
+
const [arg] = node.arguments;
|
|
75126
|
+
if (!methods.has(node.expression.name.text) || !isStringArg(arg) || arg.text !== kebab) return;
|
|
75127
|
+
calls.push({ uri, file, receiver: node.expression.expression, arg });
|
|
75128
|
+
});
|
|
74969
75129
|
}
|
|
75130
|
+
const matched = await Promise.all(
|
|
75131
|
+
calls.map(async ({ receiver }) => isRelated(await project.checker.getTypeAtLocation(receiver)))
|
|
75132
|
+
);
|
|
75133
|
+
calls.forEach(({ uri, file, arg }, i) => {
|
|
75134
|
+
if (matched[i]) result.push({ uri, range: nodeRange2(file, arg, 1), newText: asKebab });
|
|
75135
|
+
});
|
|
74970
75136
|
return result;
|
|
74971
75137
|
}
|
|
74972
|
-
function getPropRenameEdits(
|
|
75138
|
+
function getPropRenameEdits(references, newName) {
|
|
74973
75139
|
const changes = {};
|
|
74974
|
-
for (const { uri, range,
|
|
74975
|
-
const newText = form[0] === "." ? newName : camelToKebabCase(newName);
|
|
74976
|
-
(changes[uri] ??= []).push({ range, newText });
|
|
74977
|
-
}
|
|
75140
|
+
for (const { uri, range, newText } of references) (changes[uri] ??= []).push({ range, newText: newText(newName) });
|
|
74978
75141
|
return changes;
|
|
74979
75142
|
}
|
|
74980
75143
|
|
|
@@ -75046,7 +75209,7 @@ function mergeWorkspaceEdit(edit, changes) {
|
|
|
75046
75209
|
for (const [uri, edits] of Object.entries(changes)) merged[uri] = [...merged[uri] ?? [], ...edits];
|
|
75047
75210
|
return { ...edit, changes: merged };
|
|
75048
75211
|
}
|
|
75049
|
-
function createGemMiddleware(getApi, getConfig = () => defaultConfiguration) {
|
|
75212
|
+
function createGemMiddleware(getApi, getConfig = () => defaultConfiguration, renameDeclaration) {
|
|
75050
75213
|
let rules;
|
|
75051
75214
|
const getRules = () => {
|
|
75052
75215
|
const config = getConfig().elementDefineRules;
|
|
@@ -75075,7 +75238,18 @@ function createGemMiddleware(getApi, getConfig = () => defaultConfiguration) {
|
|
|
75075
75238
|
const offset = toOffset(file.text, position);
|
|
75076
75239
|
const keys2 = await getClassMapKeys(project, file, offset, css) ?? await getThemeKeys(project, file, offset);
|
|
75077
75240
|
if (keys2?.length) {
|
|
75078
|
-
|
|
75241
|
+
const items2 = keys2.map((label) => ({
|
|
75242
|
+
label,
|
|
75243
|
+
kind: ENUM_MEMBER_KIND,
|
|
75244
|
+
sortText: "",
|
|
75245
|
+
data: {
|
|
75246
|
+
fileName: file.fileName,
|
|
75247
|
+
position: offset,
|
|
75248
|
+
name: `gem:${label}`,
|
|
75249
|
+
gem: true
|
|
75250
|
+
}
|
|
75251
|
+
}));
|
|
75252
|
+
return { isIncomplete: false, items: items2 };
|
|
75079
75253
|
}
|
|
75080
75254
|
const items = Array.isArray(result) ? result : result?.items;
|
|
75081
75255
|
const never = items?.length ? await getNeverMembers(project, file, offset) : void 0;
|
|
@@ -75109,6 +75283,17 @@ function createGemMiddleware(getApi, getConfig = () => defaultConfiguration) {
|
|
|
75109
75283
|
targetSelectionRange: range
|
|
75110
75284
|
}));
|
|
75111
75285
|
}
|
|
75286
|
+
async function getStringTagDefinition(uri, position) {
|
|
75287
|
+
const ctx = await getFileContext(uri);
|
|
75288
|
+
if (!ctx) return;
|
|
75289
|
+
const { project, file } = ctx;
|
|
75290
|
+
const offset = toOffset(file.text, position);
|
|
75291
|
+
const tag = !findTemplate(file, offset) && findTagAt(file, offset);
|
|
75292
|
+
if (!tag || tag.isDefinition) return;
|
|
75293
|
+
const elementType = await resolveElementType(project, file, await index.get(project), tag.tag);
|
|
75294
|
+
const declaration = await elementType?.symbol?.declarations[0]?.resolve(project);
|
|
75295
|
+
return declaration ? [toLocationLink(tag.range, declaration)] : [];
|
|
75296
|
+
}
|
|
75112
75297
|
async function getCssReferences(uri, position) {
|
|
75113
75298
|
const ctx = await getCssContext(uri, position);
|
|
75114
75299
|
if (!ctx) return;
|
|
@@ -75125,6 +75310,23 @@ function createGemMiddleware(getApi, getConfig = () => defaultConfiguration) {
|
|
|
75125
75310
|
const prop = ctx && findElementProp(ctx.file, toOffset(ctx.file.text, position));
|
|
75126
75311
|
return prop && { ...ctx, prop };
|
|
75127
75312
|
}
|
|
75313
|
+
async function getAttributeContext(uri, position) {
|
|
75314
|
+
if (!renameDeclaration) return;
|
|
75315
|
+
const ctx = await getTemplateContext(uri, position);
|
|
75316
|
+
if (ctx?.template.kind !== "html") return;
|
|
75317
|
+
const attr = await html.attributeAt(ctx.project, ctx.template, ctx.offset);
|
|
75318
|
+
if (!attr || !ts.ast.isPropertyDeclaration(attr.declaration)) return;
|
|
75319
|
+
const file = attr.declaration.getSourceFile();
|
|
75320
|
+
if (isDepFile(file.fileName)) return;
|
|
75321
|
+
const nameStart = attr.declaration.name.getStart(file);
|
|
75322
|
+
const prop = findElementProp(file, nameStart);
|
|
75323
|
+
if (!prop) return;
|
|
75324
|
+
const declaration = {
|
|
75325
|
+
textDocument: { uri: ts.api.fileNameToDocumentURI(file.fileName) },
|
|
75326
|
+
position: toPosition(file.text, nameStart)
|
|
75327
|
+
};
|
|
75328
|
+
return { ...ctx, attr, prop, declaration };
|
|
75329
|
+
}
|
|
75128
75330
|
async function getClassNameReferences(uri, position) {
|
|
75129
75331
|
const ctx = await getFileContext(uri);
|
|
75130
75332
|
if (!ctx) return;
|
|
@@ -75155,7 +75357,7 @@ function createGemMiddleware(getApi, getConfig = () => defaultConfiguration) {
|
|
|
75155
75357
|
const template = findTemplate(file, offset);
|
|
75156
75358
|
return template && { project, template, offset };
|
|
75157
75359
|
}
|
|
75158
|
-
|
|
75360
|
+
const middleware = {
|
|
75159
75361
|
"textDocument/hover": async (result, { params }) => {
|
|
75160
75362
|
const ctx = await getTemplateContext(params.textDocument.uri, params.position);
|
|
75161
75363
|
if (!ctx) return result;
|
|
@@ -75205,6 +75407,8 @@ function createGemMiddleware(getApi, getConfig = () => defaultConfiguration) {
|
|
|
75205
75407
|
targetSelectionRange: range
|
|
75206
75408
|
}));
|
|
75207
75409
|
}
|
|
75410
|
+
const stringTagDefinition = await getStringTagDefinition(params.textDocument.uri, params.position);
|
|
75411
|
+
if (stringTagDefinition) return stringTagDefinition;
|
|
75208
75412
|
const cssDefinition = await getCssDefinition(params.textDocument.uri, params.position);
|
|
75209
75413
|
if (cssDefinition) return cssDefinition;
|
|
75210
75414
|
const ctx = await getTemplateContext(params.textDocument.uri, params.position);
|
|
@@ -75216,8 +75420,8 @@ function createGemMiddleware(getApi, getConfig = () => defaultConfiguration) {
|
|
|
75216
75420
|
if (ctx) return findTagLocations(ctx.project, ctx.info.tag, css);
|
|
75217
75421
|
const propCtx = await getPropContext(params.textDocument.uri, params.position);
|
|
75218
75422
|
if (propCtx) {
|
|
75219
|
-
const
|
|
75220
|
-
return [...result ?? [], ...
|
|
75423
|
+
const references = await findPropReferences(propCtx.project, propCtx.prop, index);
|
|
75424
|
+
return [...result ?? [], ...references.map(({ uri, range }) => ({ uri, range }))];
|
|
75221
75425
|
}
|
|
75222
75426
|
return await getCssReferences(params.textDocument.uri, params.position) ?? await getClassNameReferences(params.textDocument.uri, params.position) ?? result;
|
|
75223
75427
|
},
|
|
@@ -75226,7 +75430,9 @@ function createGemMiddleware(getApi, getConfig = () => defaultConfiguration) {
|
|
|
75226
75430
|
if (ctx) return { range: ctx.info.range, placeholder: ctx.info.tag };
|
|
75227
75431
|
const cssCtx = await getCssContext(params.textDocument.uri, params.position);
|
|
75228
75432
|
const prop = cssCtx && css.customPropertyAt(cssCtx.vDoc, cssCtx.offset);
|
|
75229
|
-
|
|
75433
|
+
if (prop) return { range: prop.range, placeholder: prop.name };
|
|
75434
|
+
const attrCtx = await getAttributeContext(params.textDocument.uri, params.position);
|
|
75435
|
+
return attrCtx ? { range: attrCtx.attr.range, placeholder: attrCtx.attr.name } : result;
|
|
75230
75436
|
},
|
|
75231
75437
|
// 在定义处重命名所有使用的地方,在模板中只重命名当前元素的开始和结束标签
|
|
75232
75438
|
"textDocument/rename": async (result, { params }) => {
|
|
@@ -75240,11 +75446,19 @@ function createGemMiddleware(getApi, getConfig = () => defaultConfiguration) {
|
|
|
75240
75446
|
const locations2 = [...declarations, ...references].map((range) => ({ uri: textDocument.uri, range }));
|
|
75241
75447
|
return { changes: toTextEdits(locations2, name) };
|
|
75242
75448
|
}
|
|
75449
|
+
const attrCtx = !ctx && await getAttributeContext(textDocument.uri, position);
|
|
75450
|
+
if (attrCtx) {
|
|
75451
|
+
const { project, attr, prop, declaration } = attrCtx;
|
|
75452
|
+
const propName = attr.isProperty ? newName : kebabToCamelCase(newName);
|
|
75453
|
+
const edit = await renameDeclaration({ ...declaration, newName: propName });
|
|
75454
|
+
const references = await findPropReferences(project, prop, index);
|
|
75455
|
+
return mergeWorkspaceEdit(edit ?? { changes: {} }, getPropRenameEdits(references, propName));
|
|
75456
|
+
}
|
|
75243
75457
|
if (!ctx) {
|
|
75244
75458
|
const propCtx = await getPropContext(textDocument.uri, position);
|
|
75245
75459
|
if (!propCtx || !result) return result;
|
|
75246
|
-
const
|
|
75247
|
-
return mergeWorkspaceEdit(result, getPropRenameEdits(
|
|
75460
|
+
const references = await findPropReferences(propCtx.project, propCtx.prop, index);
|
|
75461
|
+
return mergeWorkspaceEdit(result, getPropRenameEdits(references, newName));
|
|
75248
75462
|
}
|
|
75249
75463
|
const { info } = ctx;
|
|
75250
75464
|
const locations = info.isDefinition ? await findTagLocations(ctx.project, info.tag, css) : info.tagRanges.map((range) => ({ uri: textDocument.uri, range }));
|
|
@@ -75284,6 +75498,7 @@ function createGemMiddleware(getApi, getConfig = () => defaultConfiguration) {
|
|
|
75284
75498
|
return fixes.length ? [...result ?? [], ...fixes] : result;
|
|
75285
75499
|
}
|
|
75286
75500
|
};
|
|
75501
|
+
return { middleware, invalidate: (uri) => index.invalidate(uri) };
|
|
75287
75502
|
}
|
|
75288
75503
|
|
|
75289
75504
|
// src/bin.ts
|
|
@@ -75305,6 +75520,17 @@ function startNoopServer(clientReader2, clientWriter2) {
|
|
|
75305
75520
|
}
|
|
75306
75521
|
});
|
|
75307
75522
|
}
|
|
75523
|
+
function getChangedFiles({ method, params }) {
|
|
75524
|
+
switch (method) {
|
|
75525
|
+
case "textDocument/didChange":
|
|
75526
|
+
case "textDocument/didClose":
|
|
75527
|
+
return [params.textDocument.uri];
|
|
75528
|
+
case "workspace/didChangeWatchedFiles":
|
|
75529
|
+
return params.changes.map(({ uri }) => uri);
|
|
75530
|
+
default:
|
|
75531
|
+
return [];
|
|
75532
|
+
}
|
|
75533
|
+
}
|
|
75308
75534
|
async function startProxy(tsDir2, clientReader2, clientWriter2) {
|
|
75309
75535
|
const modules = await loadTsModules(createTypeScriptModuleLoader(path.join(tsDir2, "package.json")));
|
|
75310
75536
|
const { default: getExePath } = await import(pathToFileURL2(path.join(tsDir2, "lib/getExePath.js")).href);
|
|
@@ -75325,9 +75551,10 @@ async function startProxy(tsDir2, clientReader2, clientWriter2) {
|
|
|
75325
75551
|
const updateConfig = (gem) => {
|
|
75326
75552
|
if (gem) config = { ...defaultConfiguration, ...gem };
|
|
75327
75553
|
};
|
|
75328
|
-
const middleware = createGemMiddleware(
|
|
75554
|
+
const { middleware, invalidate } = createGemMiddleware(
|
|
75329
75555
|
() => api,
|
|
75330
|
-
() => config
|
|
75556
|
+
() => config,
|
|
75557
|
+
(params) => request("textDocument/rename", params)
|
|
75331
75558
|
);
|
|
75332
75559
|
const pendingRequests = /* @__PURE__ */ new Map();
|
|
75333
75560
|
clientReader2.listen(async (msg) => {
|
|
@@ -75337,6 +75564,7 @@ async function startProxy(tsDir2, clientReader2, clientWriter2) {
|
|
|
75337
75564
|
if (import_node.Message.isNotification(msg) && msg.method === "workspace/didChangeConfiguration") {
|
|
75338
75565
|
updateConfig(msg.params.settings?.gem);
|
|
75339
75566
|
}
|
|
75567
|
+
if (import_node.Message.isNotification(msg)) for (const uri of getChangedFiles(msg)) invalidate(uri);
|
|
75340
75568
|
if (import_node.Message.isRequest(msg) && msg.method in middleware) pendingRequests.set(msg.id, msg);
|
|
75341
75569
|
serverWriter.write(msg);
|
|
75342
75570
|
if (import_node.Message.isNotification(msg) && msg.method === "initialized") {
|