lit-analyzer
Version:
CLI that type checks bindings in lit-html templates
135 lines (134 loc) • 6.85 kB
JavaScript
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
var html_tag_js_1 = require("../analyze/parse/parse-html-data/html-tag.js");
var html_node_attr_assignment_types_js_1 = require("../analyze/types/html-node/html-node-attr-assignment-types.js");
var html_node_attr_types_js_1 = require("../analyze/types/html-node/html-node-attr-types.js");
var html_node_types_js_1 = require("../analyze/types/html-node/html-node-types.js");
var attribute_util_js_1 = require("../analyze/util/attribute-util.js");
var range_util_js_1 = require("../analyze/util/range-util.js");
/**
* This rule validates that only known attributes are used in attribute bindings.
*/
var rule = {
id: "no-unknown-attribute",
meta: {
priority: "low"
},
visitHtmlAttribute: function (htmlAttr, context) {
var _a;
var htmlStore = context.htmlStore, config = context.config, definitionStore = context.definitionStore;
// Ignore "style" and "svg" attrs because I don't yet have all data for them.
if (htmlAttr.htmlNode.kind !== html_node_types_js_1.HtmlNodeKind.NODE)
return;
// Only validate attribute bindings.
if (htmlAttr.kind !== html_node_attr_types_js_1.HtmlNodeAttrKind.ATTRIBUTE && htmlAttr.kind !== html_node_attr_types_js_1.HtmlNodeAttrKind.BOOLEAN_ATTRIBUTE)
return;
// Report a diagnostic if the target is unknown
var htmlAttrTarget = htmlStore.getHtmlAttrTarget(htmlAttr);
if (htmlAttrTarget == null) {
// Don't report unknown attributes on unknown tag names
var htmlTag = htmlStore.getHtmlTag(htmlAttr.htmlNode);
if (htmlTag == null)
return;
// Ignore unknown "data-" attributes
if (htmlAttr.name.startsWith("data-"))
return;
// Ignore element expressions
if (((_a = htmlAttr.assignment) === null || _a === void 0 ? void 0 : _a.kind) === html_node_attr_assignment_types_js_1.HtmlNodeAttrAssignmentKind.ELEMENT_EXPRESSION)
return;
// Get suggested target
var suggestedTarget = (0, attribute_util_js_1.suggestTargetForHtmlAttr)(htmlAttr, htmlStore);
var suggestedModifier_1 = suggestedTarget == null ? undefined : (0, html_tag_js_1.litAttributeModifierForTarget)(suggestedTarget);
var suggestedMemberName_1 = suggestedTarget == null ? undefined : suggestedTarget.name;
var suggestion = getSuggestionText({ config: config, htmlTag: htmlTag, definitionStore: definitionStore });
context.report({
location: (0, range_util_js_1.rangeFromHtmlNodeAttr)(htmlAttr),
message: "Unknown attribute '".concat(htmlAttr.name, "'."),
fixMessage: suggestedMemberName_1 == null ? undefined : "Did you mean '".concat(suggestedModifier_1).concat(suggestedMemberName_1, "'?"),
suggestion: suggestion,
fix: function () {
return __spreadArray([
{
message: "Change attribute to 'data-".concat(htmlAttr.name, "'"),
actions: [
{
kind: "changeAttributeName",
newName: "data-".concat(htmlAttr.name),
htmlAttr: htmlAttr
}
]
}
], __read((suggestedMemberName_1 == null
? []
: [
{
message: "Change attribute to '".concat(suggestedModifier_1).concat(suggestedMemberName_1, "'"),
actions: [
{
kind: "changeAttributeName",
newName: suggestedMemberName_1,
htmlAttr: htmlAttr
},
{
kind: "changeAttributeModifier",
newModifier: suggestedModifier_1,
htmlAttr: htmlAttr
}
]
}
])), false);
}
});
}
return;
}
};
exports.default = rule;
/**
* Returns are suggestion for the unknown attribute rule.
* @param config
* @param definitionStore
* @param htmlTag
*/
function getSuggestionText(_a) {
var _b, _c;
var config = _a.config, definitionStore = _a.definitionStore, htmlTag = _a.htmlTag;
if (config.dontSuggestConfigChanges) {
return "Please consider using a data-* attribute.";
}
var tagHasDeclaration = htmlTag.declaration != null;
var tagIsBuiltIn = htmlTag.builtIn || false;
var tagIsFromLibrary = ((_c = (_b = definitionStore.getDefinitionForTagName(htmlTag.tagName)) === null || _b === void 0 ? void 0 : _b.sourceFile) === null || _c === void 0 ? void 0 : _c.isDeclarationFile) || false;
return tagIsBuiltIn
? "This is a built in tag. Please consider using a 'data-*' attribute, adding the attribute to 'globalAttributes' or disabling the 'no-unknown-attribute' rule."
: tagIsFromLibrary
? "If you are not the author of this component please consider using a 'data-*' attribute, adding the attribute to 'globalAttributes' or disabling the 'no-unknown-attribute' rule."
: tagHasDeclaration
? "Please consider adding it as an attribute on the component, adding '@attr' tag to jsdoc on the component class or using a 'data-*' attribute instead."
: "Please consider using a 'data-*' attribute.";
}
;