| // Copyright 2026 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import {existsSync, readFileSync} from 'node:fs'; | |
| import * as path from 'node:path'; | |
| import {fileURLToPath} from 'node:url'; | |
| import * as ts from 'typescript'; | |
| import {generateExactTestId} from '../front_end/testing/TestIdGeneration.ts'; | |
| const filename = fileURLToPath(import.meta.url); | |
| const dirname = path.dirname(filename); | |
| const REPO_ROOT = path.resolve(dirname, '..'); | |
| const [filePath, lineStr, colStr] = process.argv.slice(2); | |
| if (!filePath || !lineStr || !colStr) { | |
| console.error('Usage: node scripts/get_test_id.ts <file> <line> <column>'); | |
| process.exit(1); | |
| } | |
| const line = parseInt(lineStr, 10); | |
| const column = parseInt(colStr, 10); | |
| const absFilePath = path.resolve(process.cwd(), filePath); | |
| if (!existsSync(absFilePath)) { | |
| console.error(`Error: File not found at '${absFilePath}'`); | |
| process.exit(1); | |
| } | |
| const fileContent = readFileSync(absFilePath, 'utf-8'); | |
| const sourceFile = ts.createSourceFile( | |
| absFilePath, | |
| fileContent, | |
| ts.ScriptTarget.ESNext, | |
| /* setParentNodes */ true, | |
| ); | |
| const lineIndex = Math.max(0, line - 1); | |
| const colIndex = Math.max(0, column - 1); | |
| const targetOffset = sourceFile.getPositionOfLineAndCharacter(lineIndex, colIndex); | |
| function findTestTitlePath(sf: ts.SourceFile, offset: number): string[] { | |
| const titlePath: string[] = []; | |
| function isTestOrSuiteCall(node: ts.Node): node is ts.CallExpression { | |
| if (!ts.isCallExpression(node)) { | |
| return false; | |
| } | |
| const exprText = node.expression.getText(sf); | |
| return /^(describe|it)($|\.|\w+)/.test(exprText); | |
| } | |
| function getStringArgument(callNode: ts.CallExpression): string|null { | |
| if (callNode.arguments.length === 0) { | |
| return null; | |
| } | |
| const firstArg = callNode.arguments[0]; | |
| if (ts.isStringLiteral(firstArg) || ts.isNoSubstitutionTemplateLiteral(firstArg)) { | |
| return firstArg.text; | |
| } | |
| if (ts.isTemplateExpression(firstArg)) { | |
| return firstArg.getText(sf).replace(/^`|`$/g, ''); | |
| } | |
| return null; | |
| } | |
| function visit(node: ts.Node) { | |
| if (node.getStart(sf) <= offset && offset <= node.getEnd()) { | |
| if (isTestOrSuiteCall(node)) { | |
| const title = getStringArgument(node); | |
| if (title !== null) { | |
| titlePath.push(title); | |
| } | |
| } | |
| ts.forEachChild(node, visit); | |
| } | |
| } | |
| visit(sourceFile); | |
| return titlePath; | |
| } | |
| const titlePath = findTestTitlePath(sourceFile, targetOffset); | |
| const relativeSourceFile = path.relative(REPO_ROOT, absFilePath).replace(/\\/g, '/'); | |
| const {exactTestId} = generateExactTestId(REPO_ROOT, relativeSourceFile, titlePath); | |
| console.log(exactTestId); |