UNPKG

chrome-devtools-frontend

Version:
110 lines (96 loc) 4.04 kB
// Copyright 2025 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. /** * @fileoverview Prefer `urlString` helper over sprinkling casts to * `UrlString` across the codebase.. */ import type {TSESLint, TSESTree} from '@typescript-eslint/utils'; import {createRule} from './utils/ruleCreator.ts'; // Define specific node types for clarity type ImportDeclaration = TSESTree.ImportDeclaration; type TSAsExpression = TSESTree.TSAsExpression; type TSTypeReference = TSESTree.TSTypeReference; type RuleFix = TSESLint.RuleFix; export default createRule({ name: 'prefer-url-string', meta: { type: 'suggestion', docs: { description: 'Prefer `urlString` helper over manual casts to `UrlString` branded type.', category: 'Best Practices', }, messages: { useUrlString: 'Use `urlString` helper instead of explicitly casting strings to the `UrlString` branded type', }, fixable: 'code', schema: [], // no options }, defaultOptions: [], create: function(context) { const sourceCode = context.sourceCode; let lastImportNode: ImportDeclaration|null = null; let importTypeStarAsPlatformNode: ImportDeclaration|null = null; function isImportTypeStarAsPlatform(node: ImportDeclaration): boolean { return node.importKind === 'type' && node.specifiers.length === 1 && node.specifiers[0].type === 'ImportNamespaceSpecifier' && node.specifiers[0].local.type === 'Identifier' && node.specifiers[0].local.name === 'Platform'; } function isPlatformDevToolsPathUrlString(node: TSESTree.TypeNode): node is TSTypeReference { // Check if it's a TSTypeReference with a TSQualifiedName if (node.type !== 'TSTypeReference' || node.typeName.type !== 'TSQualifiedName') { return false; } const typeName = node.typeName; // Check Platform.DevToolsPath part if (typeName.left.type !== 'TSQualifiedName' || typeName.left.left.type !== 'Identifier' || typeName.left.left.name !== 'Platform' || typeName.left.right.type !== 'Identifier' || typeName.left.right.name !== 'DevToolsPath') { return false; } // Check UrlString part return typeName.right.type === 'Identifier' && typeName.right.name === 'UrlString'; } function reportError(node: TSAsExpression) { context.report({ node, messageId: 'useUrlString', fix(fixer) { const urlText = node.expression.type === 'Literal' ? String(node.expression.value).replace('$', '\\$').replace('`', '\\`') : `\${${sourceCode.getText(node.expression)}}`; const nodeText = `urlString\`${urlText}\``; const fixers: RuleFix[] = [fixer.replaceText(node, nodeText)]; // Add import fix if needed if (lastImportNode !== null) { fixers.push(fixer.insertTextAfter(lastImportNode, '\n\nconst {urlString} = Platform.DevToolsPath;')); // Prevent adding the import fix multiple times within the same file run lastImportNode = null; } // Modify type import if needed if (importTypeStarAsPlatformNode) { const importStarAsPlatformText = sourceCode.getText(importTypeStarAsPlatformNode).replace(/import\s+type/, 'import'); fixers.push(fixer.replaceText(importTypeStarAsPlatformNode, importStarAsPlatformText)); // Prevent modifying the import multiple times importTypeStarAsPlatformNode = null; } return fixers; }, }); } return { ImportDeclaration(node) { lastImportNode = node; if (isImportTypeStarAsPlatform(node)) { importTypeStarAsPlatformNode = node; } }, TSAsExpression(node) { if (isPlatformDevToolsPathUrlString(node.typeAnnotation)) { reportError(node); } }, }; }, });