UNPKG

lit-analyzer

Version:

CLI that type checks bindings in lit-html templates

151 lines (150 loc) 8.04 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; Object.defineProperty(exports, "__esModule", { value: true }); var ast_util_js_1 = require("../analyze/util/ast-util.js"); var range_util_js_1 = require("../analyze/util/range-util.js"); /** * Returns the identifier of the decorator used on the member if any * @param member * @param context */ var getDecoratorIdentifier = function (member, context) { var _a, _b; var decorator = (_b = (_a = member.meta) === null || _a === void 0 ? void 0 : _a.node) === null || _b === void 0 ? void 0 : _b.decorator; if (decorator == null) { return undefined; } return (0, ast_util_js_1.getNodeIdentifier)(decorator, context.ts); }; /** * This rule detects mismatches with property visibilities and the decorators * they were defined with. */ var rule = { id: "no-property-visibility-mismatch", meta: { priority: "medium" }, visitComponentMember: function (member, context) { // Only run this rule on members of "property" kind if (member.kind !== "property") { return; } // Get the decorator of the property if any var decoratorIdentifier = getDecoratorIdentifier(member, context); if (decoratorIdentifier == null || decoratorIdentifier.getSourceFile() !== context.file) { return; } // Get the decorator of interest var decoratorName = decoratorIdentifier.text; var hasInternalDecorator = decoratorName === "internalProperty"; var hasPropertyDecorator = decoratorName === "property"; // Handle cases where @internalProperty decorator is used, but the property is public if (hasInternalDecorator && (member.visibility === "public" || member.visibility == null)) { var inJsFile = context.file.fileName.endsWith(".js"); context.report(__assign({ location: (0, range_util_js_1.rangeFromNode)(decoratorIdentifier), message: "'".concat(member.propName, "' is marked as an internal property (@internalProperty) but is publicly accessible.") }, (inJsFile ? { // We are in Javascript context. Add "@properted" or "@private" JSDoc } : { // We are in Typescript context. Add "protected" or "private" keyword fixMessage: "Change the property access to 'private' or 'protected'?", fix: function () { var _a; // Make sure we operate on a property declaration var propertyDeclaration = member.node; if (!context.ts.isPropertyDeclaration(propertyDeclaration)) { return []; } // The modifiers the user can choose to add to fix this warning/error var modifiers = ["protected", "private"]; // Get the public modifier if any. If one exists, we want to change that one. var publicModifier = (_a = propertyDeclaration.modifiers) === null || _a === void 0 ? void 0 : _a.find(function (modifier) { return modifier.kind === context.ts.SyntaxKind.PublicKeyword; }); if (publicModifier != null) { // Return actions that can replace the modifier return modifiers.map(function (keyword) { return ({ message: "Change to '".concat(keyword, "'"), actions: [ { kind: "changeRange", range: (0, range_util_js_1.rangeFromNode)(publicModifier), newText: keyword } ] }); }); } // If there is no existing visibility modifier, add a new modifier right in front of the property name (identifier) var propertyIdentifier = propertyDeclaration.name; if (propertyIdentifier != null) { // Return actions that can add a modifier in front of the identifier return modifiers.map(function (keyword) { return ({ message: "Add '".concat(keyword, "' modifier"), actions: [ { kind: "changeRange", range: (0, range_util_js_1.makeSourceFileRange)({ start: propertyIdentifier.getStart(), end: propertyIdentifier.getStart() }), newText: "".concat(keyword, " ") } ] }); }); } return []; } }))); } // Handle cases where @property decorator is used, but the property is not public else if (hasPropertyDecorator && member.visibility !== "public") { context.report({ location: (0, range_util_js_1.rangeFromNode)(decoratorIdentifier), message: "'".concat(member.propName, "' is not publicly accessible but is marked as a public property (@property)."), fixMessage: "Use the '@internalProperty' decorator instead?", fix: function () { var _a; // Return a code action that can replace the identifier of the decorator var newText = "internalProperty"; // Change identifier to "internal property" var actions = [ { kind: "changeIdentifier", identifier: decoratorIdentifier, newText: newText } ]; // Find the object literal node (the config of the "@property" decorator) var objectLiteralNode = (0, ast_util_js_1.findChild)(decoratorIdentifier.parent, function (node) { return context.ts.isObjectLiteralExpression(node); }); if (objectLiteralNode != null) { // Remove the configuration if the config doesn't have any shared properties with the "internalProperty" config var internalPropertyConfigProperties_1 = ["hasChanged"]; if (!((_a = objectLiteralNode.properties) === null || _a === void 0 ? void 0 : _a.some(function (propertyNode) { var _a; return internalPropertyConfigProperties_1.includes(((_a = propertyNode.name) === null || _a === void 0 ? void 0 : _a.getText()) || ""); }))) { actions.push({ kind: "changeRange", range: (0, range_util_js_1.rangeFromNode)(objectLiteralNode), newText: "" }); } } return { message: "Change to '".concat(newText, "'"), actions: actions }; } }); } } }; exports.default = rule;