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 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
- #cache = /* @__PURE__ */ new Map();
28935
+ #rules;
28936
+ #projects = /* @__PURE__ */ new Map();
28932
28937
  constructor(getRules) {
28933
28938
  this.#getRules = getRules;
28934
28939
  }
28935
- async get(project) {
28936
- let entry = this.#cache.get(project.id);
28937
- if (!entry) this.#cache.set(project.id, entry = {});
28938
- const current = entry;
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
- async #scan(project) {
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
- const elements = /* @__PURE__ */ new Map();
28947
- for (const file of files) {
28948
- if (!file) continue;
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
- return elements;
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 = ts.ast.getTokenAtPosition(file, offset);
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 elementType2 = await resolveElementType(project, template.file, await this.#index.get(project), tag);
74767
- const declaration2 = await elementType2?.symbol?.declarations[0]?.resolve(project);
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(attr));
74792
- const declaration = await prop?.declarations[0]?.resolve(project);
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 = ts.ast.getTokenAtPosition(file, offset);
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", "property"];
75005
+ var ATTRIBUTE_DECORATORS = ["attribute", "numattribute", "boolattribute"];
75006
+ var PROPERTY_DECORATORS = ["property"];
74912
75007
  var EMITTER_DECORATORS = ["emitter", "globalemitter"];
74913
- function getAttrForms(name, decorators) {
74914
- const kebab = camelToKebabCase(name);
74915
- if (decorators.some((d) => EMITTER_DECORATORS.includes(d))) return [["@", kebab]];
74916
- if (decorators.some((d) => ATTRIBUTE_DECORATORS.includes(d))) {
74917
- return [
74918
- ["", kebab],
74919
- ["?", kebab],
74920
- [".", name]
74921
- ];
74922
- }
74923
- return [];
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 = ts.ast.getTokenAtPosition(file, offset);
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 forms = getAttrForms(token.text, getDecoratorNames(decl));
74932
- return forms.length ? { name: token, element, forms } : void 0;
75038
+ const kind = getPropKind(getDecoratorNames(decl));
75039
+ return kind && { name: token, element, kind };
74933
75040
  }
74934
- async function getElementTags(project, element, index) {
74935
- const { checker } = project;
74936
- const file = element.getSourceFile();
74937
- const elementType = await checker.getTypeAtLocation(element.name ?? element);
74938
- const elements = await index.get(project);
74939
- const tags = await Promise.all(
74940
- [...elements.keys()].map(async (tag) => {
74941
- const type = await resolveElementType(project, file, elements, tag);
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
- async function findPropAttributes(project, prop, index) {
74954
- const tags = await getElementTags(project, prop.element, index);
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 form of prop.forms) {
74962
- const [decorate, name] = form;
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, form, range: template.toRangeFromOffsets(info.start + decorate.length, info.end) });
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(attributes, newName) {
75138
+ function getPropRenameEdits(references, newName) {
74973
75139
  const changes = {};
74974
- for (const { uri, range, form } of attributes) {
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
- return { isIncomplete: false, items: keys2.map((label) => ({ label, kind: ENUM_MEMBER_KIND, sortText: "" })) };
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
- return {
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 attributes = await findPropAttributes(propCtx.project, propCtx.prop, index);
75220
- return [...result ?? [], ...attributes.map(({ uri, range }) => ({ uri, range }))];
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
- return prop ? { range: prop.range, placeholder: prop.name } : result;
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 attributes = await findPropAttributes(propCtx.project, propCtx.prop, index);
75247
- return mergeWorkspaceEdit(result, getPropRenameEdits(attributes, newName));
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") {