ts-gem-plugin 0.0.9 → 0.0.12
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/index.js +215 -68
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -27,6 +27,26 @@ var import_typescript_template_language_service_decorator = require("@mantou/typ
|
|
|
27
27
|
|
|
28
28
|
// ../gem/lib/utils.js
|
|
29
29
|
var { assign, fromEntries, entries, keys } = Object;
|
|
30
|
+
function queueMicrotask(cb) {
|
|
31
|
+
Promise.resolve().then(cb);
|
|
32
|
+
}
|
|
33
|
+
var microtaskSet = /* @__PURE__ */ new Set();
|
|
34
|
+
function addMicrotask(func, method = queueMicrotask) {
|
|
35
|
+
if (microtaskSet.has(func))
|
|
36
|
+
return;
|
|
37
|
+
method(() => {
|
|
38
|
+
microtaskSet.delete(func);
|
|
39
|
+
func();
|
|
40
|
+
});
|
|
41
|
+
microtaskSet.add(func);
|
|
42
|
+
}
|
|
43
|
+
function createUpdater(initState, fn) {
|
|
44
|
+
const state = fn;
|
|
45
|
+
delete state.name;
|
|
46
|
+
delete state.length;
|
|
47
|
+
assign(state, initState);
|
|
48
|
+
return state;
|
|
49
|
+
}
|
|
30
50
|
var LinkedList = class extends EventTarget {
|
|
31
51
|
#map = /* @__PURE__ */ new Map();
|
|
32
52
|
#firstItem;
|
|
@@ -101,6 +121,10 @@ var LinkedList = class extends EventTarget {
|
|
|
101
121
|
}
|
|
102
122
|
return deleteItem;
|
|
103
123
|
}
|
|
124
|
+
clear() {
|
|
125
|
+
this.#map.clear();
|
|
126
|
+
this.#firstItem = this.#lastItem = void 0;
|
|
127
|
+
}
|
|
104
128
|
/** 获取头部元素,会从链表删除 */
|
|
105
129
|
get() {
|
|
106
130
|
const firstItem = this.#firstItem;
|
|
@@ -117,8 +141,25 @@ function kebabToCamelCase(str) {
|
|
|
117
141
|
return str.replace(/-(.)/g, (_substr, $1) => $1.toUpperCase());
|
|
118
142
|
}
|
|
119
143
|
|
|
144
|
+
// ../gem/lib/store.js
|
|
145
|
+
var _StoreListenerMap = /* @__PURE__ */ new WeakMap();
|
|
146
|
+
function createStore(originStore) {
|
|
147
|
+
const store = createUpdater(originStore, (value) => {
|
|
148
|
+
Object.assign(store, value);
|
|
149
|
+
_StoreListenerMap.get(store)?.forEach((func) => addMicrotask(func));
|
|
150
|
+
});
|
|
151
|
+
_StoreListenerMap.set(store, /* @__PURE__ */ new Set());
|
|
152
|
+
return store;
|
|
153
|
+
}
|
|
154
|
+
function connect(store, func) {
|
|
155
|
+
const listeners = _StoreListenerMap.get(store);
|
|
156
|
+
listeners?.add(func);
|
|
157
|
+
return () => listeners?.delete(func);
|
|
158
|
+
}
|
|
159
|
+
|
|
120
160
|
// src/configuration.ts
|
|
121
161
|
var defaultConfiguration = {
|
|
162
|
+
strict: false,
|
|
122
163
|
emmet: {},
|
|
123
164
|
elementDefineRules: {
|
|
124
165
|
"Duoyun*Element": "dy-*",
|
|
@@ -140,26 +181,27 @@ var Rules = class {
|
|
|
140
181
|
}
|
|
141
182
|
}
|
|
142
183
|
};
|
|
184
|
+
var configurationStore = createStore({});
|
|
185
|
+
function onConfigurationUpdate(fn) {
|
|
186
|
+
return connect(configurationStore, fn);
|
|
187
|
+
}
|
|
143
188
|
var Configuration = class {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
this
|
|
189
|
+
strict = defaultConfiguration.strict;
|
|
190
|
+
emmet = defaultConfiguration.emmet;
|
|
191
|
+
elementDefineRules = new Rules(defaultConfiguration.elementDefineRules);
|
|
192
|
+
update({ update, ...config }) {
|
|
193
|
+
Object.assign(this, defaultConfiguration, config);
|
|
194
|
+
this.elementDefineRules = new Rules({
|
|
149
195
|
...defaultConfiguration.elementDefineRules,
|
|
150
196
|
...config.elementDefineRules
|
|
151
197
|
});
|
|
152
|
-
|
|
153
|
-
get emmet() {
|
|
154
|
-
return this.#emmet;
|
|
155
|
-
}
|
|
156
|
-
get elementDefineRules() {
|
|
157
|
-
return this.#elementDefineRules;
|
|
198
|
+
configurationStore();
|
|
158
199
|
}
|
|
159
200
|
};
|
|
160
201
|
|
|
161
202
|
// src/constants.ts
|
|
162
203
|
var NAME = "gem-plugin";
|
|
204
|
+
var HTML_SUBSTITUTION_CHAR = "_";
|
|
163
205
|
var Decorators = {
|
|
164
206
|
Attr: "attribute",
|
|
165
207
|
NumAttr: "numattribute",
|
|
@@ -168,6 +210,7 @@ var Decorators = {
|
|
|
168
210
|
Emitter: "emitter",
|
|
169
211
|
GlobalEmitter: "globalemitter",
|
|
170
212
|
AdoptedStyle: "adoptedStyle",
|
|
213
|
+
Shadow: "shadow",
|
|
171
214
|
CustomElement: "customElement",
|
|
172
215
|
Part: "part",
|
|
173
216
|
Slot: "slot"
|
|
@@ -279,6 +322,11 @@ var Cache = class {
|
|
|
279
322
|
this.#addedLinked.add(value);
|
|
280
323
|
return value;
|
|
281
324
|
}
|
|
325
|
+
clear() {
|
|
326
|
+
this.#map.clear();
|
|
327
|
+
this.#reverseMap.clear();
|
|
328
|
+
this.#addedLinked.clear();
|
|
329
|
+
}
|
|
282
330
|
};
|
|
283
331
|
|
|
284
332
|
// src/cache.ts
|
|
@@ -293,6 +341,9 @@ var LRUCache = class {
|
|
|
293
341
|
get(context, position, init) {
|
|
294
342
|
return this.#bucket.get(this.#genKey(context, position), init);
|
|
295
343
|
}
|
|
344
|
+
clear() {
|
|
345
|
+
this.#bucket.clear?.();
|
|
346
|
+
}
|
|
296
347
|
};
|
|
297
348
|
|
|
298
349
|
// src/data-provider.ts
|
|
@@ -318,6 +369,16 @@ function forEachNode(roots, fn) {
|
|
|
318
369
|
list.push(..."getChildren" in currentNode ? currentNode.getChildren() : currentNode.children);
|
|
319
370
|
}
|
|
320
371
|
}
|
|
372
|
+
function hasDecorator(typescript, node, decorates) {
|
|
373
|
+
return node.modifiers?.some(
|
|
374
|
+
(modifier) => typescript.isDecorator(modifier) && typescript.isIdentifier(modifier.expression) && (!decorates || decorates.includes(modifier.expression.text))
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
function hasCallDecorator(typescript, node, decorates) {
|
|
378
|
+
return node.modifiers?.some(
|
|
379
|
+
(modifier) => typescript.isDecorator(modifier) && typescript.isCallExpression(modifier.expression) && (!decorates || decorates.includes(modifier.expression.expression.getText()))
|
|
380
|
+
);
|
|
381
|
+
}
|
|
321
382
|
function getTemplateNode(typescript, node) {
|
|
322
383
|
if (typescript.isTaggedTemplateExpression(node)) return node.template;
|
|
323
384
|
if (typescript.isTemplateExpression(node)) return node;
|
|
@@ -469,7 +530,7 @@ var HTMLDataProvider = class {
|
|
|
469
530
|
const declaration = e.getDeclarations()?.at(0);
|
|
470
531
|
const prop = declaration && ts.isPropertyDeclaration(declaration);
|
|
471
532
|
if (!prop) return;
|
|
472
|
-
const hasPropDecorator =
|
|
533
|
+
const hasPropDecorator = hasDecorator(ts, declaration);
|
|
473
534
|
if (!hasPropDecorator && !isDep) return;
|
|
474
535
|
const type = declaration.type && typeChecker.getTypeFromTypeNode(declaration.type);
|
|
475
536
|
const typeText = declaration.type?.getText();
|
|
@@ -644,7 +705,7 @@ var Context = class {
|
|
|
644
705
|
const vHtml = this.htmlLanguageService.parseHTMLDocument(vDoc);
|
|
645
706
|
const tagNodeMap = new NodeMap();
|
|
646
707
|
const classIdNodeMap = new NodeMap();
|
|
647
|
-
vHtml.roots.forEach(function
|
|
708
|
+
vHtml.roots.forEach(function process(e, index, arr) {
|
|
648
709
|
e.prev = arr[index - 1];
|
|
649
710
|
e.next = arr[index + 1];
|
|
650
711
|
e.tag && tagNodeMap.add(e.tag, e);
|
|
@@ -667,7 +728,7 @@ var Context = class {
|
|
|
667
728
|
});
|
|
668
729
|
});
|
|
669
730
|
}
|
|
670
|
-
e.children.forEach(
|
|
731
|
+
e.children.forEach(process);
|
|
671
732
|
});
|
|
672
733
|
return { vDoc, vHtml, tagNodeMap, classIdNodeMap };
|
|
673
734
|
});
|
|
@@ -801,7 +862,7 @@ function createVirtualDocument(languageId, content) {
|
|
|
801
862
|
return import_vscode_html_languageservice2.TextDocument.create(`embedded://document.${languageId}`, languageId, 1, content);
|
|
802
863
|
}
|
|
803
864
|
function getSubstitution(templateString, start, end) {
|
|
804
|
-
return templateString.slice(start, end).replaceAll(/[^\n]/g,
|
|
865
|
+
return templateString.slice(start, end).replaceAll(/[^\n]/g, HTML_SUBSTITUTION_CHAR);
|
|
805
866
|
}
|
|
806
867
|
function isValidCSSTemplate(typescript, node, callName) {
|
|
807
868
|
switch (node.kind) {
|
|
@@ -1067,6 +1128,7 @@ var CSSLanguageService = class {
|
|
|
1067
1128
|
#ctx;
|
|
1068
1129
|
constructor(ctx) {
|
|
1069
1130
|
this.#ctx = ctx;
|
|
1131
|
+
onConfigurationUpdate(() => this.#diagnosticsCache.clear());
|
|
1070
1132
|
}
|
|
1071
1133
|
#normalize(context, position) {
|
|
1072
1134
|
const parent = context.node.parent;
|
|
@@ -1117,20 +1179,40 @@ var CSSLanguageService = class {
|
|
|
1117
1179
|
#getSyntacticDiagnostics(context) {
|
|
1118
1180
|
const { text, offset } = this.#normalize(context, { line: 0, character: 0 });
|
|
1119
1181
|
const { vDoc, vCss } = this.#ctx.getCssDoc(text);
|
|
1182
|
+
const result = [];
|
|
1120
1183
|
const oDiagnostics = this.#ctx.cssLanguageService.doValidation(vDoc, vCss);
|
|
1121
1184
|
const file = this.#ctx.getProgram().getSourceFile(context.fileName);
|
|
1122
|
-
|
|
1185
|
+
const baseDiagnostic = { code: 0, file, source: NAME, category: context.typescript.DiagnosticCategory.Warning };
|
|
1186
|
+
if (this.#ctx.config.strict) {
|
|
1187
|
+
const scopeSelectors = /* @__PURE__ */ new Set([":host", ":scope", "&"]);
|
|
1188
|
+
vCss.getChildren().forEach((rule) => {
|
|
1189
|
+
if (rule.type !== import_vscode_css_languageservice2.NodeType.Ruleset) return;
|
|
1190
|
+
const [selectors, declarations] = rule.getChildren();
|
|
1191
|
+
const isScopeSelector = selectors?.getChildren().every((s) => scopeSelectors.has(s.getText()));
|
|
1192
|
+
if (!isScopeSelector) return;
|
|
1193
|
+
declarations.getChildren().forEach((decl) => {
|
|
1194
|
+
const [property] = decl.getChildren();
|
|
1195
|
+
if (property?.getText() === "display") {
|
|
1196
|
+
result.push({
|
|
1197
|
+
...baseDiagnostic,
|
|
1198
|
+
start: property.offset,
|
|
1199
|
+
length: property.length,
|
|
1200
|
+
messageText: `Do not set 'display' directly in '${selectors.getText()}', which will cause the 'hidden' attribute to be unavailable`
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
});
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
oDiagnostics.forEach(({ message, range }) => {
|
|
1123
1207
|
const start = context.toOffset(range.start);
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
code: 0,
|
|
1127
|
-
file,
|
|
1208
|
+
result.push({
|
|
1209
|
+
...baseDiagnostic,
|
|
1128
1210
|
start: range.start.line === 0 ? start - offset : start,
|
|
1129
1211
|
length: context.toOffset(range.end) - start,
|
|
1130
|
-
messageText: message
|
|
1131
|
-
|
|
1132
|
-
};
|
|
1212
|
+
messageText: message
|
|
1213
|
+
});
|
|
1133
1214
|
});
|
|
1215
|
+
return result;
|
|
1134
1216
|
}
|
|
1135
1217
|
getSyntacticDiagnostics(context) {
|
|
1136
1218
|
this.#ctx.initElements();
|
|
@@ -1345,36 +1427,44 @@ var HTMLLanguageService = class {
|
|
|
1345
1427
|
}
|
|
1346
1428
|
#getHtmlSemanticDiagnostics(context) {
|
|
1347
1429
|
const offset = context.node.getStart() + 1;
|
|
1430
|
+
const templateTag = context.node.parent.tag?.getText();
|
|
1348
1431
|
const { vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1349
1432
|
const program = this.#ctx.getProgram();
|
|
1350
1433
|
const file = program.getSourceFile(context.fileName);
|
|
1351
1434
|
const typeChecker = program.getTypeChecker();
|
|
1352
1435
|
const diagnostics = [];
|
|
1436
|
+
const baseDiagnostic = { file, source: NAME };
|
|
1353
1437
|
forEachNode(vHtml.roots, (node) => {
|
|
1354
1438
|
if (!node.tag) return;
|
|
1355
1439
|
const customElementTagDecl = this.#ctx.elements.get(node.tag);
|
|
1356
1440
|
const builtInElementTagDecl = this.#ctx.builtInElements.get(node.tag);
|
|
1441
|
+
const baseTagDiagnostic = { ...baseDiagnostic, start: node.start + 1, length: node.tag.length };
|
|
1442
|
+
if (this.#ctx.config.strict && templateTag !== "raw" && node.tag === "style") {
|
|
1443
|
+
diagnostics.push({
|
|
1444
|
+
...baseTagDiagnostic,
|
|
1445
|
+
category: context.typescript.DiagnosticCategory.Warning,
|
|
1446
|
+
code: 106 /* NoStyleTag */,
|
|
1447
|
+
messageText: `Use 'adoptedStyle' or 'createDecoratorTheme' instead of the style tag`
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1357
1450
|
if (isCustomElementTag(node.tag) && !customElementTagDecl) {
|
|
1358
1451
|
diagnostics.push({
|
|
1452
|
+
...baseTagDiagnostic,
|
|
1359
1453
|
category: context.typescript.DiagnosticCategory.Warning,
|
|
1360
1454
|
code: 101 /* UnknownTag */,
|
|
1361
|
-
|
|
1362
|
-
start: node.start + 1,
|
|
1363
|
-
length: node.tag.length,
|
|
1364
|
-
messageText: `Unknown element tag '${node.tag}'`,
|
|
1365
|
-
source: NAME
|
|
1455
|
+
messageText: `Unknown element tag '${node.tag}'`
|
|
1366
1456
|
});
|
|
1367
1457
|
}
|
|
1368
1458
|
const tagDeclaration = customElementTagDecl || builtInElementTagDecl;
|
|
1369
1459
|
if (!tagDeclaration) return;
|
|
1460
|
+
const classType = typeChecker.getTypeAtLocation(tagDeclaration);
|
|
1461
|
+
const isSVG = builtInElementTagDecl?.name.getText().startsWith("SVG");
|
|
1370
1462
|
if (tagDeclaration.name && isDeprecate(typeChecker.getSymbolAtLocation(tagDeclaration.name))) {
|
|
1371
|
-
[node.start + 1, node.endTagStart && node.endTagStart + 2].forEach((tagStart) => {
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
file,
|
|
1463
|
+
[node.start + 1, node.endTagStart && node.endTagStart + 2].filter(isNotNullish).forEach((tagStart) => {
|
|
1464
|
+
diagnostics.push({
|
|
1465
|
+
...baseTagDiagnostic,
|
|
1375
1466
|
start: tagStart,
|
|
1376
|
-
|
|
1377
|
-
source: NAME,
|
|
1467
|
+
category: context.typescript.DiagnosticCategory.Suggestion,
|
|
1378
1468
|
code: 105 /* Deprecated */,
|
|
1379
1469
|
messageText: `Deprecated tag '${node.tag}'`,
|
|
1380
1470
|
reportsDeprecated: "true"
|
|
@@ -1382,30 +1472,45 @@ var HTMLLanguageService = class {
|
|
|
1382
1472
|
});
|
|
1383
1473
|
}
|
|
1384
1474
|
for (const [attributeName, { value, start, end }] of node.attributesMap) {
|
|
1385
|
-
if (attributeName.startsWith(
|
|
1386
|
-
const hasValueSpan = value?.startsWith(
|
|
1475
|
+
if (attributeName.startsWith(HTML_SUBSTITUTION_CHAR)) continue;
|
|
1476
|
+
const hasValueSpan = value?.startsWith(HTML_SUBSTITUTION_CHAR);
|
|
1387
1477
|
const attrInfo = getAttrName(attributeName);
|
|
1388
|
-
const
|
|
1478
|
+
const propName = getPropName(attrInfo, !!builtInElementTagDecl);
|
|
1479
|
+
const propType = getPropType(typeChecker, classType, propName, attrInfo.decorate === "@");
|
|
1480
|
+
const attrBaseDiagnostic = { ...baseDiagnostic, start, length: end - start };
|
|
1481
|
+
if (isDeprecate(classType.getProperty(propName))) {
|
|
1389
1482
|
diagnostics.push({
|
|
1483
|
+
...attrBaseDiagnostic,
|
|
1390
1484
|
category: context.typescript.DiagnosticCategory.Suggestion,
|
|
1391
|
-
file,
|
|
1392
|
-
start,
|
|
1393
|
-
length: end - start,
|
|
1394
|
-
source: NAME,
|
|
1395
1485
|
code: 105 /* Deprecated */,
|
|
1396
1486
|
messageText: `Deprecated prop '${attrInfo.attr}'`,
|
|
1397
1487
|
reportsDeprecated: "true"
|
|
1398
1488
|
});
|
|
1399
|
-
}
|
|
1489
|
+
}
|
|
1400
1490
|
const diagnostic = {
|
|
1491
|
+
...attrBaseDiagnostic,
|
|
1401
1492
|
category: context.typescript.DiagnosticCategory.Warning,
|
|
1402
|
-
file,
|
|
1403
|
-
start,
|
|
1404
|
-
length: end - start,
|
|
1405
|
-
source: NAME,
|
|
1406
1493
|
code: 103 /* PropTypeError */,
|
|
1407
1494
|
messageText: !propType ? `'${attributeName}' type error` : `'${attributeName}' not satisfied '${typeChecker.typeToString(propType)}'`
|
|
1408
1495
|
};
|
|
1496
|
+
if (templateTag === "raw") {
|
|
1497
|
+
if (attrInfo.decorate) {
|
|
1498
|
+
diagnostics.push({
|
|
1499
|
+
...diagnostic,
|
|
1500
|
+
code: 104 /* PropSyntaxError */,
|
|
1501
|
+
messageText: `Raw HTML templates only support attributes`
|
|
1502
|
+
});
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
if (hasValueSpan) {
|
|
1506
|
+
diagnostics.push({
|
|
1507
|
+
...diagnostic,
|
|
1508
|
+
code: 104 /* PropSyntaxError */,
|
|
1509
|
+
messageText: `Please wrap the raw html template attribute value with "" to avoid parsing errors when the value is an empty string`
|
|
1510
|
+
});
|
|
1511
|
+
continue;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1409
1514
|
if ((attributeName === "v-else-if" || attributeName === "v-else") && !node.prev?.attributesMap.has("v-if") && !node.prev?.attributesMap.has("v-else-if")) {
|
|
1410
1515
|
diagnostics.push({
|
|
1411
1516
|
...diagnostic,
|
|
@@ -1427,7 +1532,10 @@ var HTMLLanguageService = class {
|
|
|
1427
1532
|
}
|
|
1428
1533
|
continue;
|
|
1429
1534
|
}
|
|
1430
|
-
if (
|
|
1535
|
+
if (
|
|
1536
|
+
// SVG 大小写敏感
|
|
1537
|
+
!isSVG && attrInfo.decorate === "" && attributeName !== camelToKebabCase(attrInfo.attr)
|
|
1538
|
+
) {
|
|
1431
1539
|
diagnostics.push({
|
|
1432
1540
|
...diagnostic,
|
|
1433
1541
|
code: 2552 /* AttrFormatError */,
|
|
@@ -1436,8 +1544,10 @@ var HTMLLanguageService = class {
|
|
|
1436
1544
|
continue;
|
|
1437
1545
|
}
|
|
1438
1546
|
if (!propType) {
|
|
1439
|
-
if (
|
|
1440
|
-
|
|
1547
|
+
if (
|
|
1548
|
+
// SVG 元素有很多 css 属性,所以不检查
|
|
1549
|
+
!isSVG && attrInfo.decorate !== "@"
|
|
1550
|
+
) {
|
|
1441
1551
|
diagnostics.push({
|
|
1442
1552
|
...diagnostic,
|
|
1443
1553
|
code: 102 /* UnknownProp */,
|
|
@@ -1489,7 +1599,11 @@ var HTMLLanguageService = class {
|
|
|
1489
1599
|
}
|
|
1490
1600
|
continue;
|
|
1491
1601
|
default: {
|
|
1492
|
-
const nullablePropType = getUnionType(typeChecker, [
|
|
1602
|
+
const nullablePropType = getUnionType(typeChecker, [
|
|
1603
|
+
propType,
|
|
1604
|
+
typeChecker.getNullType(),
|
|
1605
|
+
typeChecker.getUndefinedType()
|
|
1606
|
+
]);
|
|
1493
1607
|
if (!typeChecker.isTypeAssignableTo(spanType, nullablePropType) && (!typeChecker.isTypeAssignableTo(propType, typeChecker.getStringType()) || !typeChecker.isTypeAssignableTo(spanType, typeChecker.getStringType()))) {
|
|
1494
1608
|
diagnostics.push(diagnostic);
|
|
1495
1609
|
}
|
|
@@ -1680,12 +1794,16 @@ var globalEnumeratedBooleanAttr = /* @__PURE__ */ new Map([
|
|
|
1680
1794
|
["spellcheck", []],
|
|
1681
1795
|
["contenteditable", ["plaintext-only"]]
|
|
1682
1796
|
]);
|
|
1683
|
-
function
|
|
1684
|
-
const classType = typeChecker.getTypeAtLocation(tagClassDeclaration);
|
|
1797
|
+
function getPropName(attrInfo, isNativeTag) {
|
|
1685
1798
|
if (attrInfo.attr.startsWith("data-")) {
|
|
1799
|
+
return attrInfo.attr;
|
|
1800
|
+
}
|
|
1801
|
+
return globalAttrPropMap.get(attrInfo.attr) || (isNativeTag ? buildInElementNoGlobalAttrPropMap.get(attrInfo.attr) || kebabToCamelCase(attrInfo.attr) : kebabToCamelCase(attrInfo.attr));
|
|
1802
|
+
}
|
|
1803
|
+
function getPropType(typeChecker, classType, propName, isEvent) {
|
|
1804
|
+
if (propName.startsWith("data-")) {
|
|
1686
1805
|
return typeChecker.getStringType();
|
|
1687
1806
|
}
|
|
1688
|
-
const propName = globalAttrPropMap.get(attrInfo.attr) || (isBuiltInTag ? buildInElementNoGlobalAttrPropMap.get(attrInfo.attr) || kebabToCamelCase(attrInfo.attr) : kebabToCamelCase(attrInfo.attr));
|
|
1689
1807
|
switch (propName) {
|
|
1690
1808
|
case "class":
|
|
1691
1809
|
case "style":
|
|
@@ -1718,9 +1836,7 @@ function getPropType(typeChecker, tagClassDeclaration, isBuiltInTag, attrInfo, r
|
|
|
1718
1836
|
typeChecker.getUndefinedType()
|
|
1719
1837
|
]);
|
|
1720
1838
|
default: {
|
|
1721
|
-
const isEvent = attrInfo.decorate === "@";
|
|
1722
1839
|
const propSymbol = classType.getProperty(propName);
|
|
1723
|
-
if (isDeprecate(propSymbol)) reportDeprecate();
|
|
1724
1840
|
const propType = propSymbol && typeChecker.getTypeOfSymbol(propSymbol);
|
|
1725
1841
|
if (!isEvent) return propType;
|
|
1726
1842
|
const eventHandleType = getEmitterHandleType(typeChecker, classType, propType);
|
|
@@ -1805,19 +1921,45 @@ function decorateLanguageService(ctx, languageService) {
|
|
|
1805
1921
|
ts.forEachChild(file, (node) => {
|
|
1806
1922
|
const tag = ctx.getTagFromNode(node);
|
|
1807
1923
|
if (tag && ts.isClassDeclaration(node)) {
|
|
1924
|
+
if (node.name && !node.name.text.endsWith("Element")) {
|
|
1925
|
+
result.push({
|
|
1926
|
+
file,
|
|
1927
|
+
start: node.name.getStart(),
|
|
1928
|
+
length: node.name.getEnd() - node.name.getStart(),
|
|
1929
|
+
category: ctx.config.strict ? ts.DiagnosticCategory.Warning : ts.DiagnosticCategory.Suggestion,
|
|
1930
|
+
code: 108 /* SuggestionClassName */,
|
|
1931
|
+
messageText: "Element definition class suggests the suffix to use `Element`"
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
const isShadowDom = hasCallDecorator(ts, node, [Decorators.Shadow]);
|
|
1808
1935
|
node.members.forEach((member) => {
|
|
1936
|
+
const memberStart = member.getStart();
|
|
1937
|
+
const baseMemberDiagnostic = { file, start: memberStart, length: member.getEnd() - memberStart };
|
|
1938
|
+
if (ctx.config.strict && member.name && ts.isIdentifier(member.name) && member.name.text.length > 3 && member.name.text === member.name.text.toLowerCase() && member.name.text.startsWith("on")) {
|
|
1939
|
+
result.push({
|
|
1940
|
+
...baseMemberDiagnostic,
|
|
1941
|
+
category: ts.DiagnosticCategory.Warning,
|
|
1942
|
+
code: 110 /* SuggestionPropName */,
|
|
1943
|
+
messageText: "Consider changing the name, it looks too much like the html event handler"
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1809
1946
|
if (!ts.isPropertyDeclaration(member) || !member.modifiers) return;
|
|
1810
|
-
|
|
1811
|
-
(modifier) => ts.isDecorator(modifier) && ts.isIdentifier(modifier.expression) && [Decorators.Slot, Decorators.Part].includes(modifier.expression.text)
|
|
1812
|
-
);
|
|
1813
|
-
if (shouldIsStatic && member.modifiers.every((e) => e.kind !== ts.SyntaxKind.StaticKeyword)) {
|
|
1947
|
+
if (hasDecorator(ts, member, [Decorators.Prop]) && !member.questionToken && !member.initializer) {
|
|
1814
1948
|
result.push({
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1949
|
+
...baseMemberDiagnostic,
|
|
1950
|
+
category: ctx.config.strict ? ts.DiagnosticCategory.Warning : ts.DiagnosticCategory.Suggestion,
|
|
1951
|
+
code: 109 /* SuggestionPropOptional */,
|
|
1952
|
+
messageText: "Custom element property should be optional"
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
if (!hasDecorator(ts, member, [Decorators.Slot, Decorators.Part])) return;
|
|
1956
|
+
const missStaticKeyword = member.modifiers.every((e) => e.kind !== ts.SyntaxKind.StaticKeyword);
|
|
1957
|
+
if (missStaticKeyword || !isShadowDom) {
|
|
1958
|
+
result.push({
|
|
1959
|
+
...baseMemberDiagnostic,
|
|
1818
1960
|
category: ts.DiagnosticCategory.Warning,
|
|
1819
|
-
code:
|
|
1820
|
-
messageText: "Use static field for `@part` and `@slot`"
|
|
1961
|
+
code: 107 /* DecoratorSyntaxError */,
|
|
1962
|
+
messageText: missStaticKeyword ? "Use static field for `@part` and `@slot`" : "Not available on light dom `@part` and `@slot`"
|
|
1821
1963
|
});
|
|
1822
1964
|
}
|
|
1823
1965
|
});
|
|
@@ -1826,10 +1968,15 @@ function decorateLanguageService(ctx, languageService) {
|
|
|
1826
1968
|
return result.filter(({ start, reportsUnnecessary, category }) => {
|
|
1827
1969
|
if (!reportsUnnecessary || category !== ts.DiagnosticCategory.Suggestion) return true;
|
|
1828
1970
|
const node = getAstNodeAtPosition(ts, file, start);
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
if (
|
|
1832
|
-
|
|
1971
|
+
const declaration = node?.parent;
|
|
1972
|
+
if (!declaration) return true;
|
|
1973
|
+
if (ts.isClassDeclaration(declaration)) {
|
|
1974
|
+
return !hasCallDecorator(ts, declaration, [Decorators.CustomElement]);
|
|
1975
|
+
}
|
|
1976
|
+
if (ts.isMethodDeclaration(declaration) || ts.isPropertyDeclaration(declaration)) {
|
|
1977
|
+
return !declaration.modifiers?.some((e) => e?.kind === ts.SyntaxKind.Decorator);
|
|
1978
|
+
}
|
|
1979
|
+
return true;
|
|
1833
1980
|
});
|
|
1834
1981
|
};
|
|
1835
1982
|
languageService.findReferences = (...args) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-gem-plugin",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"description": "Typescript language service plugin for Gem",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gem",
|
|
@@ -14,17 +14,17 @@
|
|
|
14
14
|
"prepublishOnly": "pnpm run build"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@mantou/gem": "^2.2.
|
|
17
|
+
"@mantou/gem": "^2.2.4",
|
|
18
18
|
"@mantou/typescript-template-language-service-decorator": "^2.3.6",
|
|
19
19
|
"@mantou/vscode-css-languageservice": "^6.3.6",
|
|
20
20
|
"@mantou/vscode-emmet-helper": "^2.9.3",
|
|
21
21
|
"@mantou/vscode-html-languageservice": "^5.3.6",
|
|
22
|
-
"duoyun-ui": "^2.2.
|
|
22
|
+
"duoyun-ui": "^2.2.3",
|
|
23
23
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
24
24
|
"vscode-languageserver-types": "^3.17.5"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@gemjs/config": "^2.1.
|
|
27
|
+
"@gemjs/config": "^2.1.3",
|
|
28
28
|
"typescript": "^5.6.2"
|
|
29
29
|
},
|
|
30
30
|
"author": "mantou132",
|