UNPKG

chrome-devtools-frontend

Version:
159 lines (139 loc) 5.73 kB
// Copyright 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import type {TSESTree} from '@typescript-eslint/utils'; import {createRule} from './utils/ruleCreator.ts'; export default createRule({ name: 'lit-template-result-or-nothing', meta: { type: 'problem', docs: { description: 'Enforce use of Lit.LitTemplate type rather than union with Lit.nothing or {}', category: 'Possible Errors', }, fixable: 'code', messages: { useLitTemplateOverEmptyObject: 'Prefer Lit.LitTemplate type over a union with {}', useLitTemplateOverTypeOfNothing: 'Prefer Lit.LitTemplate type over a union with typeof Lit.nothing', }, schema: [], // no options }, defaultOptions: [], create: function(context) { const sourceCode = context.sourceCode; const UNION_TYPE_FOR_LIT_TEMPLATE = 'Lit.LitTemplate'; function checkUnionReturnTypeForLit(node: TSESTree.TSUnionType): void { // We want to go through the types in the union and match if: // 1. We find `Lit.TemplateResult` and `{}` // 2. We find `Lit.TemplateResult` and `typeof Lit.nothing`. // Otherwise, this node is OK. let templateResultNode: TSESTree.TSTypeReference|null = null; let literalEmptyObjectNode: TSESTree.TSTypeLiteral|null = null; let litNothingNode: TSESTree.TSTypeQuery|null = null; const nonLitRelatedNodesInUnion = new Set<TSESTree.TypeNode>(); for (const typeNode of node.types) { // This matches a type reference of X.y. Now we see if X === 'Lit' and y === 'TemplateResult' if (typeNode.type === 'TSTypeReference' && typeNode.typeName.type === 'TSQualifiedName' && typeNode.typeName.left.type === 'Identifier') { const leftText = typeNode.typeName.left.name; const rightText = typeNode.typeName.right.name; if (leftText === 'Lit' && rightText === 'TemplateResult') { templateResultNode = typeNode; continue; } } else if (typeNode.type === 'TSTypeLiteral') { // The TSTypeLiteral type matches against the literal `{}` type. literalEmptyObjectNode = typeNode; continue; } else if ( typeNode.type === 'TSTypeQuery' && typeNode.exprName.type === 'TSQualifiedName' && typeNode.exprName.left.type === 'Identifier') { // matches `typeof X.y` const leftText = typeNode.exprName.left.name; const rightText = typeNode.exprName.right.name; if (leftText === 'Lit' && rightText === 'nothing') { litNothingNode = typeNode; continue; } } nonLitRelatedNodesInUnion.add(typeNode); } // We didn't find Lit.TemplateResult, so bail. if (!templateResultNode) { return; } if (!litNothingNode && !literalEmptyObjectNode) { // We found TemplateResult with no `typeof Lit.nothing` or `{}`, so // bail. return; } // If we found a union type of: // Lit.TemplateResult|{}|number // That needs to become: // Lit.LitTemplate|number // So we capture all the non-lit related types in the union, and get // their text content, so we can keep them around when we run the fixer. const nonLitRelatedTypesToKeep = Array.from( nonLitRelatedNodesInUnion, node => { return sourceCode.getText(node); }, ); const newText = [ UNION_TYPE_FOR_LIT_TEMPLATE, ...nonLitRelatedTypesToKeep, ].join('|'); context.report({ node, messageId: litNothingNode ? 'useLitTemplateOverTypeOfNothing' : 'useLitTemplateOverEmptyObject', fix(fixer) { return fixer.replaceText(node, newText); }, }); } function checkTSTypeAnnotationForPotentialIssue(node: TSESTree.TSTypeAnnotation): void { const annotation = node.typeAnnotation; if (annotation.type === 'TSUnionType') { // matches foo(): X|Y checkUnionReturnTypeForLit(annotation); } else if (annotation.type === 'TSTypeReference') { // matches many things, including foo(): Promise<X|Y>, which we do want // to check. if (annotation.typeName.type !== 'Identifier' || annotation.typeName.name !== 'Promise') { // If it's not a promise, bail out. return; } // Represents the generic type passed to the promise: if our code is // Promise<X>, this node represents the X. const promiseGenericNode = annotation.typeArguments?.params[0]; if (promiseGenericNode && promiseGenericNode.type === 'TSUnionType') { checkUnionReturnTypeForLit(promiseGenericNode); } } } function checkFunctionDeclarationOrExpressionForUnionType( node: TSESTree.FunctionDeclaration|TSESTree.FunctionExpression|TSESTree.ArrowFunctionExpression): void { if (!node.returnType) { return; } checkTSTypeAnnotationForPotentialIssue(node.returnType); } return { // function() {} FunctionDeclaration(node) { checkFunctionDeclarationOrExpressionForUnionType(node); }, // Match functions defined inside classes FunctionExpression(node) { checkFunctionDeclarationOrExpressionForUnionType(node); }, // Match values in interfaces or types. TSPropertySignature(node) { if (!node.typeAnnotation) { return; } checkTSTypeAnnotationForPotentialIssue(node.typeAnnotation); }, }; }, });