/
githubmirror
/
grafana
Обзор
Документация
Войти
/
githubmirror
/
grafana
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
scripts/cli/analytics/findAllEvents.mts
156 строк
6 KB
Matt Cowley
Chore: Expand ESLint + lefthook coverage to all JS-alike files (#130259)
10 авг 2026, 14:07
Не верифицирован
10 авг 2026, 14:07
b2f3648
Код
Авторство
О чём код?
import { Node, SyntaxKind, type SourceFile } from 'ts-morph'; import { parseEventsFromFile } from './eventParser.mts'; import { getMetadataFromJSDocs, getJsDocsFromNode, resolveType } from './typeResolution.mts'; import type { EventData, EventNamespace, EventPropertySchema } from './types.mts'; export const findAllEvents = (files: SourceFile[], defineFeatureEventsPath: string): EventData[] => { const eventMap = new Map<string, EventData>(); for (const file of files) { const createEventFactoryImportedName = getDefineFeatureEventsLocalName(file, defineFeatureEventsPath); if (!createEventFactoryImportedName) { continue; } const eventNamespaces = findEventNamespaces(file, createEventFactoryImportedName); const events = parseEventsFromFile(file, eventNamespaces); for (const event of events) { if (eventMap.has(event.fullEventName)) { throw new Error(`Duplicate event name ${event.fullEventName} found in ${file.getFilePath()}`); } eventMap.set(event.fullEventName, event); } } return [...eventMap.values()]; }; /** * Returns the local name of `defineFeatureEvents` in this file, or null if not imported. * * This handles aliasing: * import { defineFeatureEvents as def } from '@grafana/runtime/internal' * would return "def", not "defineFeatureEvents". */ const getDefineFeatureEventsLocalName = (sourceFile: SourceFile, defineFeatureEventsPath: string): string | null => { const importDecl = sourceFile.getImportDeclaration( (decl) => decl.getModuleSpecifierValue() === defineFeatureEventsPath ); if (!importDecl) { return null; } const specifier = importDecl.getNamedImports().find((s) => s.getName() === 'defineFeatureEvents'); if (!specifier) { return null; // imported from runtime/internal but not defineFeatureEvents } // getAliasNode() returns the "def" part of `defineFeatureEvents as def`. // If there's no alias, fall back to the original name. return specifier.getAliasNode()?.getText() ?? specifier.getName(); }; /** * Finds all calls to defineFeatureEvents in a file and returns a map of: * factory variable name → { eventPrefixProject, eventPrefixFeature } * * Example: given * const createNavEvent = defineFeatureEvents('grafana', 'navigation'); * * Returns Map { "createNavEvent" → { factoryName: "createNavEvent", eventPrefixProject: "grafana", eventPrefixFeature: "navigation", defaultProperties: [...] } } */ const findEventNamespaces = (sourceFile: SourceFile, defineFeatureEventsName: string): Map<string, EventNamespace> => { const namespaces = new Map<string, EventNamespace>(); for (const variableDecl of sourceFile.getVariableDeclarations()) { const initializer = variableDecl.getInitializer(); if (!initializer || !Node.isCallExpression(initializer)) { continue; } // Is the right-hand side a call to our factory function? if (initializer.getExpression().getText() !== defineFeatureEventsName) { continue; } // Extract the two required string literal args: ('grafana', 'navigation') const [repoArg, featureArg, defaultPropsArg, factoryOptionsArg] = initializer.getArguments(); if (!repoArg || !featureArg || !Node.isStringLiteral(repoArg) || !Node.isStringLiteral(featureArg)) { throw new Error( `defineFeatureEvents must be called with two string literal arguments at ${sourceFile.getFilePath()}` ); } // Extract the optional third argument's type as default properties. // e.g. defineFeatureEvents('grafana', 'navigation', { schema_version: 1 }) // would produce [{ name: 'schema_version', type: 'number' }] let defaultProperties: EventPropertySchema[] | undefined; if (defaultPropsArg) { const defaultPropsType = defaultPropsArg.getType(); defaultProperties = defaultPropsType.getProperties().map((prop) => { const decl = prop.getDeclarations()[0]; const propType = decl ? prop.getTypeAtLocation(decl) : prop.getDeclaredType(); const description = decl && Node.isPropertySignature(decl) ? getMetadataFromJSDocs(decl.getJsDocs()).description : decl && Node.isPropertyAssignment(decl) ? getMetadataFromJSDocs(getJsDocsFromNode(decl)).description : undefined; return { name: prop.getName(), type: resolveType(propType), description, }; }); } // Extract the optional fourth argument: factoryOptions.silent. // e.g. defineFeatureEvents('grafana', 'cuj', undefined, { silent: true }) // marks every event from this factory as silent unless overridden per-event. const silent = factoryOptionsArg ? extractSilentFromOptions(factoryOptionsArg) : undefined; const factoryName = variableDecl.getName(); namespaces.set(factoryName, { factoryName, eventPrefixProject: repoArg.getLiteralText(), // "grafana" eventPrefixFeature: featureArg.getLiteralText(), // "navigation" defaultProperties, silent, }); } return namespaces; }; /** * Extracts `silent` from an object literal like `{ silent: true }`. Returns * undefined when the property is missing, not a boolean literal, or the * argument is not an object literal at all (e.g. `undefined`, a variable). */ export const extractSilentFromOptions = (optionsArg: Node): boolean | undefined => { if (!Node.isObjectLiteralExpression(optionsArg)) { return undefined; } const silentProperty = optionsArg.getProperty('silent'); if (!silentProperty || !Node.isPropertyAssignment(silentProperty)) { return undefined; } const initializer = silentProperty.getInitializer(); if (!initializer) { return undefined; } if (initializer.getKind() === SyntaxKind.TrueKeyword) { return true; } if (initializer.getKind() === SyntaxKind.FalseKeyword) { return false; } return undefined; };