diff --git a/packages/mcp-provider-code-analyzer/package.json b/packages/mcp-provider-code-analyzer/package.json index 1c61f12a..9e1b76db 100644 --- a/packages/mcp-provider-code-analyzer/package.json +++ b/packages/mcp-provider-code-analyzer/package.json @@ -14,13 +14,14 @@ "types": "dist/index.d.ts", "dependencies": { "@modelcontextprotocol/sdk": "^1.18.0", - "@salesforce/code-analyzer-core": "^0.40.0", - "@salesforce/code-analyzer-engine-api": "^0.32.0", - "@salesforce/code-analyzer-eslint-engine": "^0.37.0", - "@salesforce/code-analyzer-pmd-engine": "^0.33.0", - "@salesforce/code-analyzer-regex-engine": "^0.30.0", - "@salesforce/code-analyzer-retirejs-engine": "^0.29.0", + "@salesforce/code-analyzer-core": "^0.43.0", + "@salesforce/code-analyzer-engine-api": "^0.35.0", + "@salesforce/code-analyzer-eslint-engine": "^0.40.2", + "@salesforce/code-analyzer-pmd-engine": "^0.37.0", + "@salesforce/code-analyzer-regex-engine": "^0.33.0", + "@salesforce/code-analyzer-retirejs-engine": "^0.32.0", "@salesforce/mcp-provider-api": "^0.4.1", + "fast-xml-parser": "^5.5.3", "zod": "^3.25.76" }, "devDependencies": { @@ -41,7 +42,7 @@ "package.json" ], "scripts": { - "build": "tsc --build tsconfig.build.json --verbose", + "build": "tsc --build tsconfig.build.json --verbose && cp -R src/data dist/data && cp -R src/templates dist/templates", "clean": "tsc --build tsconfig.build.json --clean", "clean-all": "yarn clean && rimraf node_modules", "lint": "eslint **/*.ts", diff --git a/packages/mcp-provider-code-analyzer/src/actions/create-xpath-custom-rule.ts b/packages/mcp-provider-code-analyzer/src/actions/create-xpath-custom-rule.ts new file mode 100644 index 00000000..b99b0b79 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/src/actions/create-xpath-custom-rule.ts @@ -0,0 +1,215 @@ +import path from "node:path"; +import fs from "node:fs/promises"; +import { escapeXml, toSafeFilenameSlug } from "../utils.js"; + +// Creates PMD XPath ruleset XML and updates code-analyzer.yml. + +export type CreateXpathCustomRuleInput = { + xpath: string; + ruleName?: string; + description?: string; + language?: string; + engine?: string; + priority?: number; + workingDirectory?: string; +}; + +export type CreateXpathCustomRuleOutput = { + status: string; + ruleXml?: string; + rulesetPath?: string; + configPath?: string; +}; + +export interface CreateXpathCustomRuleAction { + exec(input: CreateXpathCustomRuleInput): Promise; +} + +export class CreateXpathCustomRuleActionImpl implements CreateXpathCustomRuleAction { + public async exec(input: CreateXpathCustomRuleInput): Promise { + const normalized = normalizeInput(input); + if ("error" in normalized) { + return { status: normalized.error }; + } + + const ruleXml = await buildRuleXml(normalized); + const { customRulesDir, rulesetPath, configPath } = buildPaths(normalized); + const rulesetPathForConfig = toRelativeRulesetPath(normalized.workingDirectory, rulesetPath); + + await fs.mkdir(customRulesDir, { recursive: true }); + await fs.writeFile(rulesetPath, ruleXml, "utf8"); + await upsertCodeAnalyzerConfig(configPath, rulesetPathForConfig, normalized.engine); + + return { status: "success", ruleXml, rulesetPath, configPath }; + } +} + +type NormalizedInput = { + xpath: string; + engine: string; + ruleName: string; + description: string; + language: string; + priority: number; + workingDirectory: string; +}; + +const DEFAULT_RULE_NAME = "CustomXPathRule"; +const DEFAULT_DESCRIPTION = "Generated rule from XPath"; +const DEFAULT_LANGUAGE = "apex"; +const DEFAULT_PRIORITY = 3; +const CUSTOM_RULES_DIR_NAME = "custom-rules"; + +function normalizeInput(input: CreateXpathCustomRuleInput): NormalizedInput | { error: string } { + const xpath = (input.xpath ?? "").trim(); + if (!xpath) { + return { error: "xpath is required" }; + } + + const engine = (input.engine ?? "pmd").toLowerCase(); + if (engine !== "pmd") { + return { error: `engine '${engine}' is not supported yet` }; + } + + const workingDirectory = input.workingDirectory?.trim(); + if (!workingDirectory) { + return { error: "workingDirectory is required" }; + } + + return { + xpath, + engine, + ruleName: input.ruleName?.trim() || DEFAULT_RULE_NAME, + description: input.description?.trim() || DEFAULT_DESCRIPTION, + language: (input.language ?? DEFAULT_LANGUAGE).toLowerCase(), + priority: Number.isFinite(input.priority) ? (input.priority as number) : DEFAULT_PRIORITY, + workingDirectory + }; +} + +async function buildRuleXml(input: NormalizedInput): Promise { + const templatePath = new URL("../templates/pmd-ruleset.xml", import.meta.url); + const template = await fs.readFile(templatePath, "utf8"); + return applyTemplate(template, { + rulesetName: escapeXml(input.ruleName), + rulesetDescription: escapeXml(input.description), + ruleName: escapeXml(input.ruleName), + language: escapeXml(input.language), + ruleMessage: escapeXml(input.description), + ruleDescription: escapeXml(input.description), + documentationUrl: "", + priority: String(input.priority), + xpathExpression: input.xpath, + exampleCode: "" + }); +} + +function buildPaths(input: NormalizedInput): { customRulesDir: string; rulesetPath: string; configPath: string } { + const customRulesDir = path.join(input.workingDirectory, CUSTOM_RULES_DIR_NAME); + const safeRuleName = toSafeFilenameSlug(input.ruleName); + return { + customRulesDir, + rulesetPath: path.join(customRulesDir, `${safeRuleName}-${input.engine}-rules.xml`), + configPath: path.join(input.workingDirectory, "code-analyzer.yml") + }; +} + +function toRelativeRulesetPath(workingDirectory: string, rulesetPath: string): string { + const relativePath = path.relative(workingDirectory, rulesetPath); + if (path.isAbsolute(relativePath) || relativePath.startsWith("..")) { + throw new Error("Ruleset path must remain within the workingDirectory."); + } + return relativePath.split(path.sep).join("/"); +} + +function applyTemplate(template: string, values: Record): string { + return template.replace(/\{\{(\w+)\}\}/g, (_, key: string) => values[key] ?? ""); +} + +async function upsertCodeAnalyzerConfig(configPath: string, rulesetPath: string, engine: string): Promise { + const existing = await readConfigIfExists(configPath); + if (!existing) { + await writeNewCodeAnalyzerConfig(configPath, rulesetPath, engine); + return; + } + if (existing.includes(rulesetPath)) { + return; + } + const updated = addRulesetPath(existing, rulesetPath, engine); + await fs.writeFile(configPath, updated, "utf8"); +} + +function addRulesetPath(configContent: string, rulesetPath: string, engine: string): string { + const lines = configContent.split(/\r?\n/); + const indices = findRulesetBlockIndices(lines, engine); + if (indices.customRulesetsLineIndex !== -1) { + lines.splice(indices.customRulesetsLineIndex + 1, 0, ` - "${rulesetPath}"`); + return lines.join("\n"); + } + if (indices.engineLineIndex !== -1) { + lines.splice(indices.engineLineIndex + 1, 0, " custom_rulesets:", ` - "${rulesetPath}"`); + return lines.join("\n"); + } + return appendEngineRulesetBlock(configContent, rulesetPath, engine); +} + +async function readConfigIfExists(configPath: string): Promise { + try { + return await fs.readFile(configPath, "utf8"); + } catch (error) { + const code = (error as NodeJS.ErrnoException).code; + if (code === "ENOENT") { + return null; + } + throw error; + } +} + +async function writeNewCodeAnalyzerConfig( + configPath: string, + rulesetPath: string, + engine: string +): Promise { + const templatePath = new URL("../templates/code-analyzer.yml", import.meta.url); + const template = await fs.readFile(templatePath, "utf8"); + const content = applyTemplate(template, { rulesetPath, engine }); + await fs.writeFile(configPath, content, "utf8"); +} + +function findRulesetBlockIndices( + lines: string[], + engine: string +): { enginesLineIndex: number; engineLineIndex: number; customRulesetsLineIndex: number } { + let enginesLineIndex = -1; + let engineLineIndex = -1; + let customRulesetsLineIndex = -1; + + for (let i = 0; i < lines.length; i++) { + const trimmed = lines[i].trim(); + if (trimmed === "engines:") { + enginesLineIndex = i; + continue; + } + if (trimmed === `${engine}:` && enginesLineIndex !== -1) { + engineLineIndex = i; + continue; + } + if (trimmed === "custom_rulesets:" && engineLineIndex !== -1) { + customRulesetsLineIndex = i; + break; + } + } + + return { enginesLineIndex, engineLineIndex, customRulesetsLineIndex }; +} + +function appendEngineRulesetBlock(configContent: string, rulesetPath: string, engine: string): string { + return [ + configContent.trimEnd(), + "", + "engines:", + ` ${engine}:`, + " custom_rulesets:", + ` - "${rulesetPath}"` + ].join("\n"); +} diff --git a/packages/mcp-provider-code-analyzer/src/actions/get-ast-nodes.ts b/packages/mcp-provider-code-analyzer/src/actions/get-ast-nodes.ts new file mode 100644 index 00000000..7ec3c485 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/src/actions/get-ast-nodes.ts @@ -0,0 +1,32 @@ +import { type AstNode } from "../ast/extract-ast-nodes.js"; +import { type ApexAstNodeMetadata } from "../ast/metadata/apex-ast-reference.js"; +import { PmdAstNodePipeline } from "../ast/ast-node-pipeline.js"; + +// Action that returns AST nodes plus cached metadata. +export type GetAstNodesInput = { + code: string; + language: string; +}; + +export type GetAstNodesOutput = { + status: string; + nodes: AstNode[]; + metadata: ApexAstNodeMetadata[]; +}; + +export interface GetAstNodesAction { + exec(input: GetAstNodesInput): Promise; +} + +export class GetAstNodesActionImpl implements GetAstNodesAction { + public async exec(input: GetAstNodesInput): Promise { + try { + const pipeline = new PmdAstNodePipeline(); + const { nodes, metadata } = await pipeline.run(input); + return { status: "success", nodes, metadata }; + } catch (e) { + return { status: (e as Error)?.message ?? String(e), nodes: [], metadata: [] }; + } + } +} + diff --git a/packages/mcp-provider-code-analyzer/src/ast/ast-node-pipeline.ts b/packages/mcp-provider-code-analyzer/src/ast/ast-node-pipeline.ts new file mode 100644 index 00000000..e4051486 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/src/ast/ast-node-pipeline.ts @@ -0,0 +1,52 @@ +import { extractAstNodesFromXml, type AstNode } from "./extract-ast-nodes.js"; +import { type ApexAstNodeMetadata } from "./metadata/apex-ast-reference.js"; +import { getEngineStrategy } from "../engines/engine-strategies.js"; + +// Template Method pipeline for AST XML -> nodes -> metadata. +export type AstPipelineInput = { + code: string; + language: string; +}; + +export type AstPipelineOutput = { + nodes: AstNode[]; + metadata: ApexAstNodeMetadata[]; +}; + +export abstract class AstNodePipeline { + public async run(input: AstPipelineInput): Promise { + const astXml = await this.generateAstXml(input); + const nodes = this.extractNodes(astXml); + const metadata = await this.enrichMetadata(input, nodes); + return { nodes, metadata }; + } + + protected abstract generateAstXml(input: AstPipelineInput): Promise; + + protected extractNodes(astXml: string): AstNode[] { + return extractAstNodesFromXml(astXml); + } + + protected async enrichMetadata( + _input: AstPipelineInput, + _nodes: AstNode[] + ): Promise { + return []; + } +} + +export class PmdAstNodePipeline extends AstNodePipeline { + private readonly strategy = getEngineStrategy("pmd"); + + protected async generateAstXml(input: AstPipelineInput): Promise { + return this.strategy.astGenerator.generateAstXml(input.code, input.language); + } + + protected async enrichMetadata( + input: AstPipelineInput, + nodes: AstNode[] + ): Promise { + const nodeNames = Array.from(new Set(nodes.map((node) => node.nodeName))); + return this.strategy.metadataProvider.getMetadata(input.language, nodeNames); + } +} diff --git a/packages/mcp-provider-code-analyzer/src/ast/extract-ast-nodes.ts b/packages/mcp-provider-code-analyzer/src/ast/extract-ast-nodes.ts new file mode 100644 index 00000000..32bdbfe7 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/src/ast/extract-ast-nodes.ts @@ -0,0 +1,99 @@ +import { XMLParser } from "fast-xml-parser"; +import * as fs from "node:fs"; + +// Parses PMD AST XML into a flat node list with ancestry info. +export interface AstNode { + nodeName: string; + attributes: Record; + parent?: string; + ancestors: string[]; +} + +const parser = new XMLParser({ + ignoreAttributes: false, + attributeNamePrefix: "@_", + ignoreDeclaration: true, +}); + +/** + * Recursively traverse XML object tree. + */ +function traverse( + node: any, + nodeName: string, + ancestors: string[], + parent?: string, + result: AstNode[] = [] +) { + if (typeof node !== "object" || node === null) return result; + + const attributes = collectAttributes(node); + + // Store current node + result.push({ + nodeName, + attributes, + parent, + ancestors, + }); + + traverseChildren(node, nodeName, ancestors, result); + + return result; +} + +function collectAttributes(node: Record): Record { + const attributes: Record = {}; + for (const key of Object.keys(node)) { + if (key.startsWith("@_")) { + attributes[key.substring(2)] = String(node[key]); + } + } + return attributes; +} + +function traverseChildren( + node: Record, + nodeName: string, + ancestors: string[], + result: AstNode[] +): void { + for (const key of Object.keys(node)) { + if (key.startsWith("@_") || key === "#text") continue; + + const child = node[key]; + const nextAncestors = [...ancestors, nodeName]; + + if (Array.isArray(child)) { + for (const c of child) { + traverse(c, key, nextAncestors, nodeName, result); + } + } else { + traverse(child, key, nextAncestors, nodeName, result); + } + } +} + +function parseAstXml(xml: string): AstNode[] { + const parsed = parser.parse(xml); + + const rootName = Object.keys(parsed).find((key) => key !== "?xml") ?? Object.keys(parsed)[0]; + const rootNode = parsed[rootName]; + + return traverse(rootNode, rootName, []); +} + +/** + * Load and process AST XML from a file path. + */ +export function extractAstNodes(xmlPath: string): AstNode[] { + const xml = fs.readFileSync(xmlPath, "utf8"); + return parseAstXml(xml); +} + +/** + * Process AST XML from a raw XML string. + */ +export function extractAstNodesFromXml(xml: string): AstNode[] { + return parseAstXml(xml); +} diff --git a/packages/mcp-provider-code-analyzer/src/ast/generate-ast-xml.ts b/packages/mcp-provider-code-analyzer/src/ast/generate-ast-xml.ts new file mode 100644 index 00000000..2a1a50b6 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/src/ast/generate-ast-xml.ts @@ -0,0 +1,40 @@ +import { PmdEngineAstXmlAdapter } from "./pmd-engine-adapter.js"; +import { PmdEngine } from "@salesforce/code-analyzer-pmd-engine"; + +// Cache the engine instance to avoid recreating it for each request (better performance) +let cachedPmdEngine: PmdEngine | null = null; +let cachedAdapter: PmdEngineAstXmlAdapter | null = null; + +/** + * Generates AST XML for the given source code using PMD Engine API. + * The engine instance is cached for better performance on subsequent calls. + * No longer requires PMD CLI to be installed. + */ +export async function generateAstXmlFromSource( + code: string, + language: string +): Promise { + if (!cachedPmdEngine) { + // Create PMD engine with minimal configuration needed for AST generation + // We only need java_command and empty arrays for the rest since AST generation + // doesn't require rules or custom configuration + const config = { + java_command: "java", + java_classpath_entries: [], + custom_rulesets: [], + rule_languages: ["apex", "visualforce", "xml", "html", "javascript"], + file_extensions: { + apex: [".cls", ".trigger"], + visualforce: [".page", ".component"], + xml: [".xml"], + html: [".html"], + javascript: [".js"] + } + }; + + cachedPmdEngine = new PmdEngine(config as any); + cachedAdapter = new PmdEngineAstXmlAdapter(cachedPmdEngine); + } + + return cachedAdapter!.generateAstXml(code, language); +} diff --git a/packages/mcp-provider-code-analyzer/src/ast/metadata/apex-ast-reference.ts b/packages/mcp-provider-code-analyzer/src/ast/metadata/apex-ast-reference.ts new file mode 100644 index 00000000..e016e178 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/src/ast/metadata/apex-ast-reference.ts @@ -0,0 +1,71 @@ +import fs from "node:fs/promises"; + +// Loads cached Apex AST node metadata from bundled JSON. +export type ApexAstAttribute = { + name: string; + type: string; + description: string; + inherited_from?: string; +}; + +export type ApexAstNodeMetadata = { + name: string; + description?: string; + category?: string; + extends?: string; + implements?: string[]; + attributes?: ApexAstAttribute[]; +}; + +type ApexAstReference = { + description?: string; + source?: string; + extraction_date?: string; + total_nodes?: number; + version?: string; + note?: string; + nodes: ApexAstNodeMetadata[]; +}; + +let cachedReference: ApexAstReference | undefined; + +async function loadApexAstReference(): Promise { + if (cachedReference) { + return cachedReference; + } + const fileUrl = new URL("../../data/pmd/apex-ast-reference.json", import.meta.url); + const raw = await fs.readFile(fileUrl, "utf8"); + cachedReference = JSON.parse(raw) as ApexAstReference; + return cachedReference; +} + +/** + * Fetch metadata for a set of Apex AST node names. + * - Preserves input order. + * - Ignores names that are not found. + */ +export async function getApexAstNodeMetadataByNames( + nodeNames: string[] +): Promise { + const reference = await loadApexAstReference(); + const index = new Map( + reference.nodes.map((node) => [node.name.toLowerCase(), node]) + ); + + const results: ApexAstNodeMetadata[] = []; + for (const name of nodeNames) { + const normalized = name.toLowerCase(); + let node = index.get(normalized); + if (!node) { + node = reference.nodes.find((candidate) => + (candidate.implements ?? []).some((iface) => + iface.toLowerCase().includes(`<${normalized}>`) + ) + ); + } + if (node) { + results.push(node); + } + } + return results; +} diff --git a/packages/mcp-provider-code-analyzer/src/ast/pmd-engine-adapter.ts b/packages/mcp-provider-code-analyzer/src/ast/pmd-engine-adapter.ts new file mode 100644 index 00000000..318890c0 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/src/ast/pmd-engine-adapter.ts @@ -0,0 +1,98 @@ +import os from "node:os"; +import path from "node:path"; +import fs from "node:fs/promises"; +import { PmdEngine, PmdAstDumpResults } from "@salesforce/code-analyzer-pmd-engine"; +import { getErrorMessage } from "../utils.js"; + +/** + * Interface for AST XML adapters + */ +export interface AstXmlAdapter { + generateAstXml(code: string, language: string): Promise; +} + +/** + * Adapter that uses PMD Engine's generateAst API instead of PMD CLI + */ +export class PmdEngineAstXmlAdapter implements AstXmlAdapter { + private readonly pmdEngine: PmdEngine; + + constructor(pmdEngine: PmdEngine) { + this.pmdEngine = pmdEngine; + } + + public async generateAstXml(code: string, language: string): Promise { + enforceMaxSourceSize(code); + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "pmd-ast-")); + const sourceFile = path.join(tempDir, `source.${sanitizeExtension(language)}`); + + try { + // Write source code to temp file + await fs.writeFile(sourceFile, code, "utf8"); + + // Use PMD Engine's generateAst method + const result: PmdAstDumpResults = await this.pmdEngine.generateAst( + normalizePmdLanguage(language), + sourceFile, + { + encoding: "UTF-8", + workingFolder: tempDir + } + ); + + // Handle error case + if (result.error) { + throw new Error(`PMD Engine error: ${result.error.message}`); + } + + // Return the AST XML + if (!result.ast) { + throw new Error("PMD Engine returned no AST and no error"); + } + + return result.ast.trim(); + } catch (error) { + const message = getErrorMessage(error); + throw new Error(`Failed to generate AST XML via PMD Engine: ${message}`); + } finally { + // Clean up temp directory + await fs.rm(tempDir, { recursive: true, force: true }); + } + } +} + +const MAX_SOURCE_BYTES = 1_000_000; + +function enforceMaxSourceSize(code: string): void { + const size = Buffer.byteLength(code ?? "", "utf8"); + if (size > MAX_SOURCE_BYTES) { + throw new Error(`Source exceeds ${MAX_SOURCE_BYTES} bytes. Provide a smaller snippet.`); + } +} + +function sanitizeExtension(language: string): string { + const cleaned = (language ?? "").toLowerCase().replace(/[^a-z0-9]/g, ""); + return cleaned.length > 0 ? cleaned : "txt"; +} + +/** + * Normalize language names to match PMD's expected identifiers + * PMD uses specific language IDs like "apex", "visualforce", "xml", etc. + */ +function normalizePmdLanguage(language: string): string { + const normalized = (language ?? "").toLowerCase().trim(); + + // Map common variations to PMD language IDs + const languageMap: Record = { + "apex": "apex", + "visualforce": "visualforce", + "vf": "visualforce", + "xml": "xml", + "html": "html", + "javascript": "javascript", + "js": "javascript", + "ecmascript": "javascript" + }; + + return languageMap[normalized] || normalized; +} diff --git a/packages/mcp-provider-code-analyzer/src/constants.ts b/packages/mcp-provider-code-analyzer/src/constants.ts index 1e427791..80d2930f 100644 --- a/packages/mcp-provider-code-analyzer/src/constants.ts +++ b/packages/mcp-provider-code-analyzer/src/constants.ts @@ -4,7 +4,8 @@ export const TelemetrySource = "MCP" export const McpTelemetryEvents = { ENGINE_SELECTION: 'engine_selection', ENGINE_EXECUTION: 'engine_execution', - RESULTS_QUERY: 'results_query' + RESULTS_QUERY: 'results_query', + CUSTOM_RULE_CREATED: 'custom_rule_created' } export const ENGINE_NAMES = [ @@ -91,6 +92,16 @@ export type Language = typeof LANGUAGES[number]; export const LANGUAGE_SET: ReadonlySet = new Set(LANGUAGES); +export const LANGUAGE_NAMES = { + Apex: 'apex', + CSS: 'css', + HTML: 'html', + JavaScript: 'javascript', + TypeScript: 'typescript', + Visualforce: 'visualforce', + XML: 'xml' +} as const; + export const ENGINE_SPECIFIC_TAGS = [ 'DevPreview', 'LWC' diff --git a/packages/mcp-provider-code-analyzer/src/data/pmd/apex-ast-reference.json b/packages/mcp-provider-code-analyzer/src/data/pmd/apex-ast-reference.json new file mode 100644 index 00000000..20a97200 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/src/data/pmd/apex-ast-reference.json @@ -0,0 +1,2680 @@ +{ + "description": "PMD Apex AST Nodes - Complete Reference", + "source": "Extracted from PMD 7.x source code", + "extraction_date": "2025-12-03", + "total_nodes": 97, + "version": "7.x", + "note": "Includes inherited attributes from parent classes (marked with inherited_from field)", + "nodes": [ + { + "name": "Annotation", + "description": "Represents an annotation like @AuraEnabled, @TestVisible, @InvocableMethod", + "category": "Modifiers", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Name", + "type": "string", + "description": "Returns the normalized annotation name for known, valid annotations. The normalized name is in PascalCase. If an unknown annotation is used, the raw name (as in the source code) is returned." + }, + { + "name": "@RawName", + "type": "string", + "description": "Returns the annotation name as it appears in the source code. This allows to verify the casing." + }, + { + "name": "@Image", + "type": "string", + "description": "Returns the annotation name as it appears in the source code. This allows to verify the casing." + }, + { + "name": "@isResolved", + "type": "boolean", + "description": "Returns the annotation name as it appears in the source code. This allows to verify the casing." + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "AnnotationParameter", + "description": "Represents a parameter of an annotation", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Name", + "type": "string", + "description": "The name of this node" + }, + { + "name": "@Value", + "type": "string", + "description": "The value of this node" + }, + { + "name": "@BooleanValue", + "type": "boolean", + "description": "The booleanvalue of this node" + }, + { + "name": "@Image", + "type": "string", + "description": "The image of this node" + }, + { + "name": "@Name", + "type": "boolean", + "description": "Checks whether this annotation parameter has the given name. The check is done case-insensitive.", + "parameters": [ + "@NonNull String name" + ] + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "AnonymousClass", + "description": "Represents anonymous class", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Image", + "type": "string", + "description": "The image of this node" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ApexFile", + "description": "Represents apex file", + "category": "Other", + "extends": "AbstractApexNode.Single", + "implements": [ + "RootNode" + ], + "attributes": [ + { + "name": "@AstInfo", + "type": "string", + "description": "The astinfo of this node" + }, + { + "name": "@MainNode", + "type": "string", + "description": "The mainnode of this node" + }, + { + "name": "@GlobalIssues", + "type": "array", + "description": "The globalissues of this node" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "The definingtype of this node" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ArrayLoadExpression", + "description": "Represents array load expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ArrayStoreExpression", + "description": "Represents array store expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "AssignmentExpression", + "description": "Represents assignment expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Op", + "type": "string", + "description": "The op of this node" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "BinaryExpression", + "description": "Represents a binary expression (operations with two operands like ==, !=, +, -, &&, ||)", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Op", + "type": "string", + "description": "The op of this node" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "BindExpressions", + "description": "Represents bind expressions", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "BlockStatement", + "description": "Represents block statement", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@CurlyBrace", + "type": "boolean", + "description": "Whether this node hasCurlyBrace" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "BooleanExpression", + "description": "Represents boolean expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Op", + "type": "string", + "description": "The op of this node" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "BreakStatement", + "description": "Represents break statement", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "CastExpression", + "description": "Represents cast expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Type", + "type": "string", + "description": "Returns the target type name of the cast expression." + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "CatchBlockStatement", + "description": "Represents catch block statement", + "category": "Statements", + "extends": "AbstractApexCommentContainerNode", + "implements": [], + "attributes": [ + { + "name": "@ExceptionType", + "type": "string", + "description": "The exceptiontype of this node" + }, + { + "name": "@VariableName", + "type": "string", + "description": "The variablename of this node" + }, + { + "name": "@Body", + "type": "string", + "description": "The body of this node" + }, + { + "name": "@ContainsComment", + "type": "boolean", + "description": "Returns true if this node contains a comment", + "inherited_from": "AbstractApexCommentContainerNode" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ClassRefExpression", + "description": "Represents class ref expression", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ConstructorPreamble", + "description": "Represents constructor preamble", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ConstructorPreambleStatement", + "description": "Represents constructor preamble statement", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ContinueStatement", + "description": "Represents continue statement", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "DmlDeleteStatement", + "description": "Represents a DML delete operation", + "category": "Statements", + "extends": "AbstractDmlStatement", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "DmlInsertStatement", + "description": "Represents a DML insert operation", + "category": "Statements", + "extends": "AbstractDmlStatement", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "DmlMergeStatement", + "description": "Represents dml merge statement", + "category": "Statements", + "extends": "AbstractDmlStatement", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "DmlUndeleteStatement", + "description": "Represents dml undelete statement", + "category": "Statements", + "extends": "AbstractDmlStatement", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "DmlUpdateStatement", + "description": "Represents a DML update operation", + "category": "Statements", + "extends": "AbstractDmlStatement", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "DmlUpsertStatement", + "description": "Represents dml upsert statement", + "category": "Statements", + "extends": "AbstractDmlStatement", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "DoLoopStatement", + "description": "Represents do loop statement", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ElseWhenBlock", + "description": "Represents else when block", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "EmptyReferenceExpression", + "description": "Represents empty reference expression", + "category": "Expressions", + "extends": "AbstractApexNode.Empty", + "implements": [], + "attributes": [ + { + "name": "@DefiningType", + "type": "string", + "description": "The definingtype of this node" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Empty" + } + ] + }, + { + "name": "Expression", + "description": "Represents expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ExpressionStatement", + "description": "Represents expression statement", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "Field", + "description": "Represents field", + "category": "Declarations", + "extends": "AbstractApexNode.Many", + "implements": [], + "attributes": [ + { + "name": "@Image", + "type": "string", + "description": "The image of this node" + }, + { + "name": "@Type", + "type": "string", + "description": "Returns the type name.

This includes any type arguments. (This is tested.) If the type is a primitive, its case will be normalized." + }, + { + "name": "@Modifiers", + "type": "node", + "description": "Returns the type name.

This includes any type arguments. (This is tested.) If the type is a primitive, its case will be normalized." + }, + { + "name": "@Name", + "type": "string", + "description": "Returns the type name.

This includes any type arguments. (This is tested.) If the type is a primitive, its case will be normalized." + }, + { + "name": "@Value", + "type": "string", + "description": "Returns the type name.

This includes any type arguments. (This is tested.) If the type is a primitive, its case will be normalized." + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns the type name.

This includes any type arguments. (This is tested.) If the type is a primitive, its case will be normalized." + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Many" + } + ] + }, + { + "name": "FieldDeclaration", + "description": "Represents field declaration", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Image", + "type": "string", + "description": "The image of this node" + }, + { + "name": "@Name", + "type": "string", + "description": "The name of this node" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "FieldDeclarationStatements", + "description": "Represents field declaration statements", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Modifiers", + "type": "node", + "description": "The modifiers of this node" + }, + { + "name": "@TypeName", + "type": "string", + "description": "Returns the type name.

This includes any type arguments. If the type is a primitive, its case will be normalized." + }, + { + "name": "@TypeArguments", + "type": "array", + "description": "This returns the first level of the type arguments. If there are nested types (e.g. {@code List>}), then these returned types contain themselves type arguments.

Note: This method only exists for this AST type and in no other type, even though type arguments are possible e.g. for {@link ASTVariableDeclaration#getType()}." + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ForEachStatement", + "description": "Represents for each statement", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ForLoopStatement", + "description": "Represents a for loop statement", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "FormalComment", + "description": "Represents formal comment", + "category": "Statements", + "extends": "AbstractApexNode.Empty", + "implements": [], + "attributes": [ + { + "name": "@Image", + "type": "string", + "description": "The image of this node" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Empty" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Empty" + } + ] + }, + { + "name": "IdentifierCase", + "description": "Represents identifier case", + "category": "Other", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "IfBlockStatement", + "description": "Represents an if statement block", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "IfElseBlockStatement", + "description": "Represents if else block statement", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@ElseStatement", + "type": "boolean", + "description": "Whether this node hasElseStatement" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "IllegalStoreExpression", + "description": "Represents illegal store expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "InstanceOfExpression", + "description": "Represents instance of expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "InvalidDependentCompilation", + "description": "Represents invalid dependent compilation", + "category": "Other", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Image", + "type": "string", + "description": "The image of this node" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "JavaMethodCallExpression", + "description": "Represents java method call expression", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "JavaVariableExpression", + "description": "Represents java variable expression", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "LiteralCase", + "description": "Represents literal case", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "LiteralExpression", + "description": "Represents a literal value (string, number, boolean, null)", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@LiteralType", + "type": "string", + "description": "The literaltype of this node" + }, + { + "name": "@isString", + "type": "boolean", + "description": "Whether this node isString" + }, + { + "name": "@isBoolean", + "type": "boolean", + "description": "Whether this node isBoolean" + }, + { + "name": "@isInteger", + "type": "boolean", + "description": "Whether this node isInteger" + }, + { + "name": "@isDouble", + "type": "boolean", + "description": "Whether this node isDouble" + }, + { + "name": "@isLong", + "type": "boolean", + "description": "Whether this node isLong" + }, + { + "name": "@isDecimal", + "type": "boolean", + "description": "Whether this node isDecimal" + }, + { + "name": "@isNull", + "type": "boolean", + "description": "Whether this node isNull" + }, + { + "name": "@Image", + "type": "string", + "description": "The image of this node" + }, + { + "name": "@Name", + "type": "string", + "description": "Returns the name of this literal when it is labeled in an object initializer with named arguments ({@link ASTNewKeyValueObjectExpression}).

For example, in the Apex code

{@code new X(a = 1, b = 2) }
, the {@link ASTLiteralExpression} corresponding to {@code 2} will have the {@code name} \"{@code b}\"." + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "MapEntryNode", + "description": "Represents map entry node", + "category": "Other", + "extends": "AbstractApexNode.Many", + "implements": [], + "attributes": [ + { + "name": "@Image", + "type": "string", + "description": "The image of this node" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Many" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Many" + } + ] + }, + { + "name": "Method", + "description": "Represents a method declaration", + "category": "Declarations", + "extends": "AbstractApexNode", + "implements": [ + "ApexQualifiableNode" + ], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Internal name used by the synthetic trigger method." + }, + { + "name": "@Image", + "type": "string", + "description": "Internal name used by the synthetic trigger method." + }, + { + "name": "@CanonicalName", + "type": "string", + "description": "Internal name used by the synthetic trigger method." + }, + { + "name": "@QualifiedName", + "type": "string", + "description": "Internal name used by the synthetic trigger method." + }, + { + "name": "@isConstructor", + "type": "boolean", + "description": "Internal name used by the synthetic trigger method." + }, + { + "name": "@isStaticInitializer", + "type": "boolean", + "description": "Internal name used by the synthetic trigger method." + }, + { + "name": "@Modifiers", + "type": "node", + "description": "Internal name used by the synthetic trigger method." + }, + { + "name": "@ReturnType", + "type": "string", + "description": "Returns the method return type name. This includes any type arguments. If the type is a primitive, its case will be normalized." + }, + { + "name": "@Arity", + "type": "integer", + "description": "Returns the method return type name. This includes any type arguments. If the type is a primitive, its case will be normalized." + }, + { + "name": "@isTriggerBlock", + "type": "boolean", + "description": "Checks whether this method is the synthetic trigger method." + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode" + } + ] + }, + { + "name": "MethodBlockStatement", + "description": "Represents method block statement", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "MethodCallExpression", + "description": "Represents a method call expression", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@MethodName", + "type": "string", + "description": "The {@link Identifier}s that constitute the {@link CallExpression#getReceiver() receiver} of this method call." + }, + { + "name": "@FullMethodName", + "type": "string", + "description": "The {@link Identifier}s that constitute the {@link CallExpression#getReceiver() receiver} of this method call." + }, + { + "name": "@InputParametersSize", + "type": "integer", + "description": "The {@link Identifier}s that constitute the {@link CallExpression#getReceiver() receiver} of this method call." + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "Modifier", + "description": "Represents modifier", + "category": "Modifiers", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ModifierNode", + "description": "Represents modifier node", + "category": "Modifiers", + "extends": "AbstractApexNode.Many", + "implements": [ + "AccessNode" + ], + "attributes": [ + { + "name": "@Modifiers", + "type": "integer", + "description": "The modifiers of this node" + }, + { + "name": "@isPublic", + "type": "boolean", + "description": "Whether this node isPublic" + }, + { + "name": "@isProtected", + "type": "boolean", + "description": "Whether this node isProtected" + }, + { + "name": "@isPrivate", + "type": "boolean", + "description": "Whether this node isPrivate" + }, + { + "name": "@isAbstract", + "type": "boolean", + "description": "Whether this node isAbstract" + }, + { + "name": "@isStatic", + "type": "boolean", + "description": "Whether this node isStatic" + }, + { + "name": "@isFinal", + "type": "boolean", + "description": "Whether this node isFinal" + }, + { + "name": "@isTransient", + "type": "boolean", + "description": "Whether this node isTransient" + }, + { + "name": "@isTest", + "type": "boolean", + "description": "Returns true if function has `@isTest` annotation or `testmethod` modifier" + }, + { + "name": "@DeprecatedTestMethod", + "type": "boolean", + "description": "Returns true if function has `testmethod` modifier" + }, + { + "name": "@isTestOrTestSetup", + "type": "boolean", + "description": "Returns true if function has `testmethod` modifier" + }, + { + "name": "@isWithSharing", + "type": "boolean", + "description": "Returns true if function has `testmethod` modifier" + }, + { + "name": "@isWithoutSharing", + "type": "boolean", + "description": "Returns true if function has `testmethod` modifier" + }, + { + "name": "@isInheritedSharing", + "type": "boolean", + "description": "Returns true if function has `testmethod` modifier" + }, + { + "name": "@isWebService", + "type": "boolean", + "description": "Returns true if function has `testmethod` modifier" + }, + { + "name": "@isGlobal", + "type": "boolean", + "description": "Returns true if function has `testmethod` modifier" + }, + { + "name": "@isOverride", + "type": "boolean", + "description": "Returns true if function has `testmethod` modifier" + }, + { + "name": "@isVirtual", + "type": "boolean", + "description": "Returns true if function has `testmethod` modifier" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Many" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Many" + } + ] + }, + { + "name": "ModifierOrAnnotation", + "description": "Represents modifier or annotation", + "category": "Modifiers", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "MultiStatement", + "description": "Represents multi statement", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "NestedExpression", + "description": "Represents nested expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "NestedStoreExpression", + "description": "Represents nested store expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "NewKeyValueObjectExpression", + "description": "Represents new key value object expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Type", + "type": "string", + "description": "Returns the type name. This includes any type arguments. If the type is a primitive, its case will be normalized." + }, + { + "name": "@ParameterCount", + "type": "integer", + "description": "Returns the type name. This includes any type arguments. If the type is a primitive, its case will be normalized." + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "NewListInitExpression", + "description": "Represents new list init expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "NewListLiteralExpression", + "description": "Represents new list literal expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "NewMapInitExpression", + "description": "Represents new map init expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "NewMapLiteralExpression", + "description": "Represents new map literal expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "NewObjectExpression", + "description": "Represents new object expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Type", + "type": "string", + "description": "Returns the type name. This includes any type arguments. If the type is a primitive, its case will be normalized." + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "NewSetInitExpression", + "description": "Represents new set init expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "NewSetLiteralExpression", + "description": "Represents new set literal expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "PackageVersionExpression", + "description": "Represents package version expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "Parameter", + "description": "Represents parameter", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Image", + "type": "string", + "description": "The image of this node" + }, + { + "name": "@Modifiers", + "type": "node", + "description": "The modifiers of this node" + }, + { + "name": "@Type", + "type": "string", + "description": "Returns the parameter's type name.

This includes any type arguments. If the type is a primitive, its case will be normalized." + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "PostfixExpression", + "description": "Represents postfix expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Op", + "type": "string", + "description": "The op of this node" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "PrefixExpression", + "description": "Represents prefix expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Op", + "type": "string", + "description": "The op of this node" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "Property", + "description": "Represents property", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Type", + "type": "string", + "description": "Returns the property value's type name. This includes any type arguments. If the type is a primitive, its case will be normalized." + }, + { + "name": "@Modifiers", + "type": "node", + "description": "Returns the property value's type name. This includes any type arguments. If the type is a primitive, its case will be normalized." + }, + { + "name": "@FormatAccessorName", + "type": "string", + "description": "Returns the internal accessor (getter/setter) name of an {@link ASTProperty}. The accessor name is the constant {@link #ACCESSOR_PREFIX} prepended to the name of the property.", + "parameters": [ + "ASTProperty property" + ] + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ReferenceExpression", + "description": "Represents reference expression", + "category": "Expressions", + "extends": "AbstractApexNode.Many", + "implements": [], + "attributes": [ + { + "name": "@ReferenceType", + "type": "string", + "description": "The referencetype of this node" + }, + { + "name": "@Image", + "type": "string", + "description": "The image of this node" + }, + { + "name": "@Names", + "type": "array", + "description": "The names of this node" + }, + { + "name": "@isSafeNav", + "type": "boolean", + "description": "Whether this node isSafeNav" + }, + { + "name": "@isSObjectType", + "type": "boolean", + "description": "Whether this node isSObjectType" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Whether this node hasRealLoc" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Many" + } + ] + }, + { + "name": "ReturnStatement", + "description": "Represents a return statement", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "RunAsBlockStatement", + "description": "Represents run as block statement", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "SoqlExpression", + "description": "Represents a SOQL query expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Query", + "type": "string", + "description": "Returns the raw query as it appears in the source code." + }, + { + "name": "@CanonicalQuery", + "type": "string", + "description": "Returns the query with the SOQL keywords normalized as uppercase." + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "SoslExpression", + "description": "Represents a SOSL search expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Query", + "type": "string", + "description": "Returns the raw query as it appears in the source code." + }, + { + "name": "@CanonicalQuery", + "type": "string", + "description": "Returns the query with the SOSL keywords normalized as uppercase." + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "StandardCondition", + "description": "Represents standard condition", + "category": "Other", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "Statement", + "description": "Represents statement", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "StatementExecuted", + "description": "Represents statement executed", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "SuperMethodCallExpression", + "description": "Represents super method call expression", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "SuperVariableExpression", + "description": "Represents super variable expression", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "SwitchStatement", + "description": "Represents switch statement", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "TernaryExpression", + "description": "Represents ternary expression", + "category": "Expressions", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ThisMethodCallExpression", + "description": "Represents this method call expression", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ThisVariableExpression", + "description": "Represents this variable expression", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ThrowStatement", + "description": "Represents throw statement", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "TriggerVariableExpression", + "description": "Represents trigger variable expression", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "TryCatchFinallyBlockStatement", + "description": "Represents try catch finally block statement", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@TryBlock", + "type": "string", + "description": "The tryblock of this node" + }, + { + "name": "@CatchClauses", + "type": "array", + "description": "The catchclauses of this node" + }, + { + "name": "@FinallyBlock", + "type": "string", + "description": "The finallyblock of this node" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "TypeWhenBlock", + "description": "Represents type when block", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Type", + "type": "string", + "description": "Returns the when block's matching type name. This includes any type arguments. If the type is a primitive, its case will be normalized." + }, + { + "name": "@Name", + "type": "string", + "description": "Returns the when block's matching type name. This includes any type arguments. If the type is a primitive, its case will be normalized." + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "UserClass", + "description": "Represents an Apex class declaration (Note: Use UserClass, not ClassNode)", + "category": "Declarations", + "extends": "BaseApexClass", + "implements": [ + "ASTUserClassOrInterface" + ], + "attributes": [ + { + "name": "@SuperClassName", + "type": "string", + "description": "Returns the name of the superclass of this class, or an empty string if there is none. The type name does NOT include type arguments." + }, + { + "name": "@InterfaceNames", + "type": "array", + "description": "Returns a list of the names of the interfaces implemented by this class. The type names do NOT include type arguments. (This is tested.)" + }, + { + "name": "@Image", + "type": "string", + "description": "Returns the name of this class/interface/enum/trigger", + "inherited_from": "BaseApexClass" + }, + { + "name": "@SimpleName", + "type": "string", + "description": "Returns the simple name of this type declaration", + "inherited_from": "BaseApexClass" + }, + { + "name": "@QualifiedName", + "type": "string", + "description": "Returns the fully qualified name of this type", + "inherited_from": "BaseApexClass" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "UserClassMethods", + "description": "Represents user class methods", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "UserEnum", + "description": "Represents an Apex enum declaration", + "category": "Declarations", + "extends": "BaseApexClass", + "implements": [], + "attributes": [ + { + "name": "@QualifiedName", + "type": "string", + "description": "The qualifiedname of this node" + }, + { + "name": "@Image", + "type": "string", + "description": "Returns the name of this class/interface/enum/trigger", + "inherited_from": "BaseApexClass" + }, + { + "name": "@SimpleName", + "type": "string", + "description": "Returns the simple name of this type declaration", + "inherited_from": "BaseApexClass" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "UserExceptionMethods", + "description": "Represents user exception methods", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "UserInterface", + "description": "Represents an Apex interface declaration", + "category": "Declarations", + "extends": "BaseApexClass", + "implements": [ + "ASTUserClassOrInterface" + ], + "attributes": [ + { + "name": "@SuperInterfaceName", + "type": "string", + "description": "Returns the name of the superclass of this class, or an empty string if there is none. The type name does NOT include type arguments." + }, + { + "name": "@Image", + "type": "string", + "description": "Returns the name of this class/interface/enum/trigger", + "inherited_from": "BaseApexClass" + }, + { + "name": "@SimpleName", + "type": "string", + "description": "Returns the simple name of this type declaration", + "inherited_from": "BaseApexClass" + }, + { + "name": "@QualifiedName", + "type": "string", + "description": "Returns the fully qualified name of this type", + "inherited_from": "BaseApexClass" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "UserTrigger", + "description": "Represents an Apex trigger declaration (Note: Use UserTrigger, not TriggerNode)", + "category": "Declarations", + "extends": "BaseApexClass", + "implements": [], + "attributes": [ + { + "name": "@TargetName", + "type": "string", + "description": "The targetname of this node" + }, + { + "name": "@Usages", + "type": "array", + "description": "The usages of this node" + }, + { + "name": "@Image", + "type": "string", + "description": "Returns the name of this class/interface/enum/trigger", + "inherited_from": "BaseApexClass" + }, + { + "name": "@SimpleName", + "type": "string", + "description": "Returns the simple name of this type declaration", + "inherited_from": "BaseApexClass" + }, + { + "name": "@QualifiedName", + "type": "string", + "description": "Returns the fully qualified name of this type", + "inherited_from": "BaseApexClass" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "ValueWhenBlock", + "description": "Represents value when block", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "VariableDeclaration", + "description": "Represents a variable declaration statement", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Image", + "type": "string", + "description": "The image of this node" + }, + { + "name": "@Type", + "type": "string", + "description": "Returns the variable's type name.

This includes any type arguments. If the type is a primitive, its case will be normalized." + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "VariableDeclarationStatements", + "description": "Represents variable declaration statements", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Modifiers", + "type": "node", + "description": "The modifiers of this node" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "VariableExpression", + "description": "Represents a reference to a variable", + "category": "Declarations", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@Image", + "type": "string", + "description": "The image of this node" + }, + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + }, + { + "name": "WhileLoopStatement", + "description": "Represents a while loop statement", + "category": "Statements", + "extends": "AbstractApexNode.Single", + "implements": [], + "attributes": [ + { + "name": "@RealLoc", + "type": "boolean", + "description": "Returns true if this node has a real source location", + "inherited_from": "AbstractApexNode.Single" + }, + { + "name": "@DefiningType", + "type": "string", + "description": "Returns the fully qualified name of the enclosing type", + "inherited_from": "AbstractApexNode.Single" + } + ] + } + ] + } \ No newline at end of file diff --git a/packages/mcp-provider-code-analyzer/src/engines/engine-strategies.ts b/packages/mcp-provider-code-analyzer/src/engines/engine-strategies.ts new file mode 100644 index 00000000..0aa5ba08 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/src/engines/engine-strategies.ts @@ -0,0 +1,190 @@ +import { generateAstXmlFromSource } from "../ast/generate-ast-xml.js"; +import { type AstNode } from "../ast/extract-ast-nodes.js"; +import { getApexAstNodeMetadataByNames, type ApexAstNodeMetadata } from "../ast/metadata/apex-ast-reference.js"; +import { LANGUAGE_NAMES } from "../constants.js"; + +type EngineName = "pmd"; + +export type PromptInput = { + language: string; + engine: string; + astNodes: AstNode[]; + astMetadata: ApexAstNodeMetadata[]; +}; + +export interface AstGenerator { + generateAstXml(code: string, language: string): Promise; +} + +export interface AstMetadataProvider { + getMetadata(language: string, nodeNames: string[]): Promise; +} + +export interface PromptBuilder { + buildPrompt(input: PromptInput): string; +} + +export type EngineStrategy = { + engine: EngineName; + astGenerator: AstGenerator; + metadataProvider: AstMetadataProvider; + promptBuilder: PromptBuilder; +}; + +class PmdAstGenerator implements AstGenerator { + public async generateAstXml(code: string, language: string): Promise { + return generateAstXmlFromSource(code, language); + } +} + +class PmdAstMetadataProvider implements AstMetadataProvider { + public async getMetadata(language: string, nodeNames: string[]): Promise { + const normalized = (language ?? "").toLowerCase().trim(); + if (normalized === LANGUAGE_NAMES.Apex) { + return getApexAstNodeMetadataByNames(nodeNames); + } + return []; + } +} + +class PmdPromptBuilder implements PromptBuilder { + public buildPrompt(input: PromptInput): string { + const nodeSummaries = buildNodeSummaries(input.astNodes, input.astMetadata); + return `You are generating a PMD XPath query. +Goal: Generate an XPath expression that matches the violation described earlier. + +Context: + +Engine: ${input.engine} + +Language: ${input.language} + +AST nodes (from ast-dump) with extracted metadata: +${JSON.stringify(nodeSummaries, null, 2)} + +Task: + +Use the AST nodes and metadata above to write a precise XPath for the violation. + +Create the XPath for the scenario described by the user request. + +Prefer minimal, stable XPath that avoids overfitting. + +Return only the XPath expression. + +Guidelines (PMD/Apex XPath): + +- Target the smallest stable ancestor that owns the behavior (e.g., MethodCallExpression). +- Avoid cross-node joins (no current(), no sibling/parent chains, no @Image equality to correlate identifiers). +- Prefer structural signals over string matching (e.g., BinaryExpression[@Op='+'], MethodCallExpression[@FullMethodName='X.Y']). +- Use pragmatic string checks only when needed (LiteralExpression guards for edge cases). +- Use only node names/attributes present in the AST dump and metadata. + +Prompt boilerplate: +- Use only node names and attributes seen in the following PMD Apex AST dump. Do not invent attributes. Treat attribute values exactly as they appear. +- Select the top-most behavior node (e.g., MethodCallExpression for Database.query/.countQuery) and match evidence of violation anywhere in its subtree using descendant axes. +- Avoid using current() and identifier equality comparisons (@Image) to correlate nodes. Do not rely on sibling or parent chains that may vary. +- Prefer simple, robust patterns over deep, brittle paths. Allow inline and variable-initialized forms. +- Return only the XPath expression, PMD-compatible, no CDATA or extra text. + +Verification checklist: +- Ensure the XPath matches: + - Inline violation: Database.query('...' + var) + - Variable-based violation: String q = '...' + var; Database.query(q) + - Multi-part concatenation chains: 'a' + b + 'c' + d + - String literal containing '+' that contributes to the query + - Both Database.query and Database.countQuery +- Ensure the XPath does not depend on variable names or declaration order. +- Prefer descendant:: over absolute paths; avoid hard-coding depths. +- If matching an API, gate on @FullMethodName='Namespace.method' not @Image. + +Pattern templates (adapt as needed): +- Method call with subtree evidence: + //MethodCallExpression[ @FullMethodName='Database.query' or @FullMethodName='Database.countQuery' ][ .//BinaryExpression[@Op='+'] or .//LiteralExpression[@LiteralType='STRING' and contains(@Image, '+')] ] +- Ban System.debug in non-test code: + //MethodCallExpression[ @FullMethodName='System.debug' ] +- Detect DML in loops: + //ForStatement | //WhileStatement | //ForEachStatement [.//MethodCallExpression[ @FullMethodName='Database.insert' or @FullMethodName='Database.update' or @FullMethodName='Database.delete' ]] +- Detect hardcoded IDs: + //LiteralExpression[ @LiteralType='STRING' and matches(@Image, '^[a-zA-Z0-9]{15,18}$') ] + +AST-first workflow: +1. Generate a minimal, compiling violating Apex snippet for the scenario. +2. Dump the PMD AST for that snippet. +3. Identify the smallest stable ancestor node to select. +4. Write XPath that: + - Filters by the ancestor’s discriminant attributes (e.g., @FullMethodName) + - Uses .// to search for evidence nodes under it (BinaryExpression, LiteralExpression, etc.) + - Avoids current() and identifier joins +5. Validate the XPath against multiple variants of the snippet. + +Acceptance test (must pass): +- Database.query('SELECT Id FROM A WHERE Name = ' + name) +- String q = 'SELECT Id FROM A WHERE Name = ' + name; Database.query(q); +- Database.countQuery('SELECT COUNT() FROM A ' + 'WHERE Type = ' + t); +- String q = 'SELECT ' + 'Id' + ' FROM A'; Database.query(q); +- Must not rely on variable names, current(), or fixed ancestor depths. + +Requirements: + +Review availableNodes (${nodeSummaries.length} nodes) to identify needed nodes. + +Use ONLY node names from availableNodes. + +Use only attributes present in the AST metadata. + +Treat attribute values exactly as shown in metadata (e.g., if @Image includes quotes, do not strip them). + +Do not invent attributes or assume normalization. + +Prefer structural matching over string manipulation. + +Avoid complex XPath functions unless clearly required. + +Ensure compatibility with PMD ${input.engine} XPath support. + +Next step: + +Call the tool 'create_custom_rule' with the generated XPath to create the custom rule.`; + } +} + +const PMD_STRATEGY: EngineStrategy = { + engine: "pmd", + astGenerator: new PmdAstGenerator(), + metadataProvider: new PmdAstMetadataProvider(), + promptBuilder: new PmdPromptBuilder() +}; + +export function getEngineStrategy(engine: string): EngineStrategy { + const normalized = (engine ?? "").toLowerCase().trim(); + if (normalized === "pmd") { + return PMD_STRATEGY; + } + throw new Error(`engine '${engine}' is not supported yet`); +} + +function buildNodeSummaries( + nodes: AstNode[], + metadata: ApexAstNodeMetadata[] +): Array<{ + nodeName: string; + parent: string | null; + ancestors: string[]; + attributes: Record; + metadata: ApexAstNodeMetadata | null; +}> { + const metadataByName = new Map( + metadata.map((node) => [node.name.toLowerCase(), node]) + ); + return nodes.map((node) => { + const nodeMetadata = metadataByName.get(node.nodeName.toLowerCase()); + return { + nodeName: node.nodeName, + parent: node.parent ?? null, + ancestors: node.ancestors, + attributes: node.attributes, + metadata: nodeMetadata ?? null + }; + }); +} diff --git a/packages/mcp-provider-code-analyzer/src/provider.ts b/packages/mcp-provider-code-analyzer/src/provider.ts index 75fd90ba..59b0a59a 100644 --- a/packages/mcp-provider-code-analyzer/src/provider.ts +++ b/packages/mcp-provider-code-analyzer/src/provider.ts @@ -9,6 +9,10 @@ import {EnginePluginsFactory, EnginePluginsFactoryImpl} from "./factories/Engine import {RunAnalyzerActionImpl} from "./actions/run-analyzer.js"; import {DescribeRuleActionImpl} from "./actions/describe-rule.js"; import { ListRulesActionImpl } from "./actions/list-rules.js"; +import { GenerateXpathPromptMcpTool } from "./tools/generate_xpath_prompt.js"; +import { CreateCustomRuleMcpTool } from "./tools/create_custom_rule.js"; +import { GetAstNodesActionImpl } from "./actions/get-ast-nodes.js"; +import { CreateXpathCustomRuleActionImpl } from "./actions/create-xpath-custom-rule.js"; export class CodeAnalyzerMcpProvider extends McpProvider { public getName(): string { @@ -34,7 +38,9 @@ export class CodeAnalyzerMcpProvider extends McpProvider { enginePluginsFactory, telemetryService: services.getTelemetryService() })), - new CodeAnalyzerQueryResultsMcpTool(new QueryResultsActionImpl(), services.getTelemetryService()) + new CodeAnalyzerQueryResultsMcpTool(new QueryResultsActionImpl(), services.getTelemetryService()), + new GenerateXpathPromptMcpTool(new GetAstNodesActionImpl(), services.getTelemetryService()), + new CreateCustomRuleMcpTool(new CreateXpathCustomRuleActionImpl(), services.getTelemetryService()) ]); } } \ No newline at end of file diff --git a/packages/mcp-provider-code-analyzer/src/templates/code-analyzer.yml b/packages/mcp-provider-code-analyzer/src/templates/code-analyzer.yml new file mode 100644 index 00000000..66a253a9 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/src/templates/code-analyzer.yml @@ -0,0 +1,5 @@ +engines: + pmd: + # Custom ruleset paths for PMD. + custom_rulesets: + - "{{rulesetPath}}" diff --git a/packages/mcp-provider-code-analyzer/src/templates/pmd-ruleset.xml b/packages/mcp-provider-code-analyzer/src/templates/pmd-ruleset.xml new file mode 100644 index 00000000..8e6286a4 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/src/templates/pmd-ruleset.xml @@ -0,0 +1,23 @@ + + + {{rulesetDescription}} + + + {{ruleDescription}} + + {{priority}} + + + + + + + + diff --git a/packages/mcp-provider-code-analyzer/src/tools/create_custom_rule.ts b/packages/mcp-provider-code-analyzer/src/tools/create_custom_rule.ts new file mode 100644 index 00000000..df33b038 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/src/tools/create_custom_rule.ts @@ -0,0 +1,166 @@ +import { z } from "zod"; +import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { McpTool, McpToolConfig, ReleaseState, Toolset, TelemetryService } from "@salesforce/mcp-provider-api"; +import * as Constants from "../constants.js"; +import { + CreateXpathCustomRuleAction, + CreateXpathCustomRuleActionImpl, + CreateXpathCustomRuleInput, + CreateXpathCustomRuleOutput +} from "../actions/create-xpath-custom-rule.js"; + +// MCP tool wrapper that validates input and delegates rule creation. +const DESCRIPTION: string = + `Purpose: Create a custom rule using a provided XPath expression. +Use this tool after an XPath has been generated for a specific violation pattern. + +If xpath is not provided and engine is "pmd": +- First call the tool "get_ast_nodes_to_generate_xpath" to generate the XPath. +- Then call this tool again with the generated XPath. + +Inputs (required): +- xpath: The XPath expression that should match the violation. +- ruleName: Name for the custom rule. +- description: A short description/message for the rule. +- language: Language for the rule (e.g., "apex"). +- engine: Engine name (e.g., "pmd"). +- priority: PMD priority (1-5). +- workingDirectory: Workspace directory where the ruleset XML and code-analyzer.yml will be created (or updated). + +Output: +- rulesetPath: Path to the generated custom ruleset XML. +- configPath: Path to the updated code-analyzer.yml that references the custom ruleset.`; + +export const inputSchema = z.object({ + xpath: z.string().optional().describe("XPath expression that should match the violation (required for PMD)."), + ruleName: z.string().describe("Name for the custom rule."), + description: z.string().describe("Short description or message for the rule."), + language: z.string().describe("Language for the rule (e.g., 'apex')."), + engine: z.string().describe("Analysis engine (e.g., 'pmd')."), + priority: z.number().int().min(1).max(5).describe("PMD priority (1-5)."), + workingDirectory: z.string().describe("Workspace directory where code-analyzer.yml will be created (or updated).") +}); +type InputArgsShape = typeof inputSchema.shape; + +const outputSchema = z.object({ + status: z.string().describe(`'success' or an error message.`), + ruleXml: z.string().optional().describe("Generated PMD ruleset XML for the custom rule."), + rulesetPath: z.string().optional().describe("Path to the generated PMD ruleset XML."), + configPath: z.string().optional().describe("Path to the generated code-analyzer.yml.") +}); +type OutputArgsShape = typeof outputSchema.shape; + +export class CreateCustomRuleMcpTool extends McpTool { + public static readonly NAME: string = "create_custom_rule"; + private readonly action: CreateXpathCustomRuleAction; + private readonly telemetryService?: TelemetryService; + + public constructor( + action: CreateXpathCustomRuleAction = new CreateXpathCustomRuleActionImpl(), + telemetryService?: TelemetryService + ) { + super(); + this.action = action; + this.telemetryService = telemetryService; + } + + public getReleaseState(): ReleaseState { + return ReleaseState.NON_GA; + } + + public getToolsets(): Toolset[] { + return [Toolset.CODE_ANALYSIS]; + } + + public getName(): string { + return CreateCustomRuleMcpTool.NAME; + } + + public getConfig(): McpToolConfig { + return { + title: "Create Custom Rule", + description: DESCRIPTION, + inputSchema: inputSchema.shape, + outputSchema: outputSchema.shape, + annotations: { + readOnlyHint: false, // Writes ruleset XML and code-analyzer.yml + destructiveHint: false, // Does not delete anything + openWorldHint: false, // Local file operations only + }, + }; + } + + public async exec(input: z.infer): Promise { + const validationError = validateInput(input); + if (validationError) { + return validationError; + } + const output: CreateXpathCustomRuleOutput = await this.action.exec(input as CreateXpathCustomRuleInput); + const message = output.rulesetPath && output.configPath + ? `Custom rule created. Ruleset: ${output.rulesetPath}. Code Analyzer config: ${output.configPath}.` + : output.status; + if (this.telemetryService && output.status === "success") { + this.telemetryService.sendEvent(Constants.TelemetryEventName, { + source: Constants.TelemetrySource, + sfcaEvent: Constants.McpTelemetryEvents.CUSTOM_RULE_CREATED, + engine: input.engine, + language: input.language, + ruleName: input.ruleName, + rulesetPath: output.rulesetPath, + configPath: output.configPath + }); + } + return { + content: [{ type: "text", text: message }], + structuredContent: output, + isError: output.status !== "success" + }; + } +} + +function validateInput(input: z.infer): CallToolResult | undefined { + const ruleName = input.ruleName?.trim(); + if (!ruleName) { + return buildError("ruleName is required. Provide a name for the custom rule."); + } + + const description = input.description?.trim(); + if (!description) { + return buildError("description is required. Provide a short description or message for the rule."); + } + + const language = input.language?.trim(); + if (!language) { + return buildError("language is required. Provide a language such as 'apex'."); + } + + const engine = input.engine?.trim(); + if (!engine) { + return buildError("engine is required. Provide an engine such as 'pmd'."); + } + + const xpath = input.xpath?.trim(); + if (engine.toLowerCase() === "pmd" && !xpath) { + return buildError("xpath is required for engine 'pmd'. Provide a valid XPath expression, use tool 'get_ast_nodes_to_generate_xpath' to generate the XPath."); + } + + if (input.priority === undefined || input.priority === null) { + return buildError("priority is required. Provide a value between 1 and 5."); + } + + const workingDirectory = input.workingDirectory?.trim(); + if (!workingDirectory) { + return buildError("workingDirectory is required. Provide a directory where files can be written."); + } + + return undefined; +} + +function buildError(status: string): CallToolResult { + const output = { status }; + return { + content: [{ type: "text", text: JSON.stringify(output) }], + structuredContent: output, + isError: true + }; +} diff --git a/packages/mcp-provider-code-analyzer/src/tools/generate_xpath_prompt.ts b/packages/mcp-provider-code-analyzer/src/tools/generate_xpath_prompt.ts new file mode 100644 index 00000000..17fe0c11 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/src/tools/generate_xpath_prompt.ts @@ -0,0 +1,166 @@ +import { z } from "zod"; +import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { McpTool, McpToolConfig, ReleaseState, TelemetryService, Toolset } from "@salesforce/mcp-provider-api"; +import * as Constants from "../constants.js"; +import { GetAstNodesActionImpl, type GetAstNodesAction, type GetAstNodesInput, type GetAstNodesOutput } from "../actions/get-ast-nodes.js"; +import { getEngineStrategy } from "../engines/engine-strategies.js"; + +// Builds the prompt that guides XPath authoring from AST context. +const DESCRIPTION: string = + `Purpose: First step for creating a PMD XPath-based custom rule. + Use this tool when the user asks to create a custom rule (especially PMD/XPath). + + Inputs (required): + - sampleCode: A short snippet that SHOULD violate the intended rule. + - Ensure this snippet truly triggers the exact pattern you want to catch. + - Keep it minimal yet self-contained (parsable/compilable); remove unrelated noise. + - Prefer realistic code over contrived examples to avoid brittle XPath. + - language: Programming language of sampleCode (e.g., "apex", "xml"). + - engine: Analysis engine (e.g., "pmd"). + + Output: + - prompt: A concise, high-signal prompt that guides an LLM to extract the AST context needed for XPath authoring from the sampleCode. + + Use this tool when the user asks for rules like: + - Ban all System.debug statements in production code. + - Enforce that all Apex classes must end with Service, Controller, Handler, or Helper suffix. + - Detect hardcoded Salesforce IDs in Apex classes. + - Require that all test methods include assertions and cannot be empty. + - Prevent usage of @future methods without proper error handling. + - Enforce that all public methods must have proper documentation comments. + - Prevent nested if statements deeper than 3 levels. + - Require that all DML operations are wrapped in try-catch blocks. + - Ensure all SOQL queries use bind variables instead of string concatenation. + - Classes implementing Batchable must have proper error handling in execute(). + - All methods with @TestVisible must be in test classes only. + - Enforce that all custom exceptions extend Exception class properly. + - Require that all Database.query calls use escapeSingleQuotes for user input. + - Ban the use of Test.isRunningTest() in production code. + + Note: This tool only prepares the prompt. A subsequent tool will use these details to generate the final XPath-based custom rule.`; + +export const inputSchema = z.object({ + sampleCode: z.string().describe("Sample code which violates the rule user is looking to generate a custom PMD rule for."), + language: z.string().describe("Programming language of the sample code (e.g., 'apex', 'javascript', 'xml')."), + engine: z.string().describe("Target analysis engine (e.g., 'pmd').") // right now it is only for pmd, but as it will be extended to other engines, it is included here +}); +type InputArgsShape = typeof inputSchema.shape; + +const outputSchema = z.object({ + status: z.string().describe(`'success' or an error message.`), + prompt: z.string().describe('Generated prompt text to guide XPath creation.') +}); +type OutputArgsShape = typeof outputSchema.shape; + +export class GenerateXpathPromptMcpTool extends McpTool { + public static readonly NAME: string = 'get_ast_nodes_to_generate_xpath'; + private readonly action: GetAstNodesAction; + private readonly telemetryService?: TelemetryService; + + public constructor( + action: GetAstNodesAction = new GetAstNodesActionImpl(), + telemetryService?: TelemetryService + ) { + super(); + this.action = action; + this.telemetryService = telemetryService; + } + public getReleaseState(): ReleaseState { + return ReleaseState.NON_GA; + } + + public getToolsets(): Toolset[] { + return [Toolset.CODE_ANALYSIS]; + } + + public getName(): string { + return GenerateXpathPromptMcpTool.NAME; + } + + public getConfig(): McpToolConfig { + return { + title: "Generate XPath Prompt", + description: DESCRIPTION, + inputSchema: inputSchema.shape, + outputSchema: outputSchema.shape, + annotations: { + readOnlyHint: false, // Creates temp files for AST generation (cleaned up after) + destructiveHint: false, // Temp files are cleaned up automatically + openWorldHint: false // Local filesystem operations only + } + }; + } + + public async exec(input: z.infer): Promise { + const validationError = validateInput(input); + if (validationError) { + return validationError; + } + + const astResult = await this.action.exec(buildAstInput(input)); + if (astResult.status !== "success") { + return buildToolResult({ status: astResult.status, prompt: "" }); + } + + const strategy = getEngineStrategy(input.engine); + const output = { + status: "success", + prompt: strategy.promptBuilder.buildPrompt({ + language: input.language, + engine: input.engine, + astNodes: astResult.nodes, + astMetadata: astResult.metadata + }) + }; + if (this.telemetryService) { + this.telemetryService.sendEvent(Constants.TelemetryEventName, { + source: Constants.TelemetrySource, + sfcaEvent: "xpath_prompt_generated", + engine: input.engine, + language: input.language + }); + } + return buildToolResult(output); + } +} + +function validateInput(input: z.infer): CallToolResult | undefined { + const language = input.language?.trim(); + if (!language) { + return buildErrorResult("language is required"); + } + + const engine = input.engine?.trim().toLowerCase(); + if (!engine) { + return buildErrorResult("engine is required"); + } + if (engine !== "pmd") { + return buildErrorResult(`engine '${engine}' is not supported yet`); + } + + const sampleCode = input.sampleCode?.trim(); + if (!sampleCode) { + return buildErrorResult(`code in ${language} is required`); + } + + return undefined; +} + +function buildAstInput(input: z.infer): GetAstNodesInput { + return { + code: input.sampleCode, + language: input.language + }; +} + +function buildErrorResult(status: string): CallToolResult { + return buildToolResult({ status, prompt: "" }); +} + +function buildToolResult(output: { status: string; prompt: string }): CallToolResult { + return { + content: [{ type: "text", text: JSON.stringify(output) }], + structuredContent: output + }; +} + diff --git a/packages/mcp-provider-code-analyzer/src/utils.ts b/packages/mcp-provider-code-analyzer/src/utils.ts index e703ff83..86047717 100644 --- a/packages/mcp-provider-code-analyzer/src/utils.ts +++ b/packages/mcp-provider-code-analyzer/src/utils.ts @@ -1,7 +1,35 @@ - +// TODO: move this file into a `src/utils/` folder in a follow-up PR. /** * Helper function to easily get an error message from a catch statement */ export function getErrorMessage(error: unknown): string { return error instanceof Error ? error.message : /* istanbul ignore next */ String(error); -} \ No newline at end of file +} + +/** + * Escape XML special characters for safe attribute/text usage. + */ +export function escapeXml(value: string): string { + return value + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll("\"", """) + .replaceAll("'", "'"); +} + +/** + * Convert a user-provided name into a safe, filesystem-friendly slug. + * - Removes path separators and invalid filename characters + * - Normalizes whitespace and repeated dashes + */ +export function toSafeFilenameSlug(value: string): string { + return value + .trim() + .replace(/[\\/:"*?<>|]+/g, "-") + .replace(/\.+/g, "-") + .replace(/\s+/g, "-") + .replace(/-+/g, "-") + .replace(/^-+|-+$/g, "") + .toLowerCase() || "custom-rule"; +} diff --git a/packages/mcp-provider-code-analyzer/test/actions/create-xpath-custom-rule.test.ts b/packages/mcp-provider-code-analyzer/test/actions/create-xpath-custom-rule.test.ts new file mode 100644 index 00000000..0b051871 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/test/actions/create-xpath-custom-rule.test.ts @@ -0,0 +1,217 @@ +import { describe, expect, it, afterEach } from "vitest"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; + +async function createTempDir(): Promise { + return fs.mkdtemp(path.join(os.tmpdir(), "mcp-custom-rule-")); +} + +async function cleanupTempDir(dir: string | undefined): Promise { + if (!dir) { + return; + } + await fs.rm(dir, { recursive: true, force: true }); +} + +function countOccurrences(haystack: string, needle: string): number { + return haystack.split(needle).length - 1; +} + +describe("CreateXpathCustomRuleActionImpl", () => { + let tempDir: string | undefined; + + afterEach(async () => { + await cleanupTempDir(tempDir); + tempDir = undefined; + }); + + it("returns an error when xpath is missing", async () => { + const { CreateXpathCustomRuleActionImpl } = await import("../../src/actions/create-xpath-custom-rule.js"); + const action = new CreateXpathCustomRuleActionImpl(); + const result = await action.exec({ + xpath: "", + engine: "pmd", + workingDirectory: "/tmp" + }); + expect(result.status).toBe("xpath is required"); + }); + + it("returns an error for unsupported engines", async () => { + const { CreateXpathCustomRuleActionImpl } = await import("../../src/actions/create-xpath-custom-rule.js"); + const action = new CreateXpathCustomRuleActionImpl(); + const result = await action.exec({ + xpath: "//MethodCallExpression", + engine: "eslint", + workingDirectory: "/tmp" + }); + expect(result.status).toBe("engine 'eslint' is not supported yet"); + }); + + it("returns an error when workingDirectory is missing", async () => { + const { CreateXpathCustomRuleActionImpl } = await import("../../src/actions/create-xpath-custom-rule.js"); + const action = new CreateXpathCustomRuleActionImpl(); + const result = await action.exec({ + xpath: "//MethodCallExpression", + engine: "pmd", + workingDirectory: " " + }); + expect(result.status).toBe("workingDirectory is required"); + }); + + it("writes ruleset XML and config with a relative ruleset path", async () => { + const { CreateXpathCustomRuleActionImpl } = await import("../../src/actions/create-xpath-custom-rule.js"); + const action = new CreateXpathCustomRuleActionImpl(); + tempDir = await createTempDir(); + + const result = await action.exec({ + xpath: "//MethodCallExpression[@FullMethodName='System.debug']", + ruleName: "My Rule", + description: "No System.debug", + language: "apex", + engine: "pmd", + priority: 2, + workingDirectory: tempDir + }); + + expect(result.status).toBe("success"); + expect(result.rulesetPath).toBeTruthy(); + expect(result.configPath).toBeTruthy(); + + const configContent = await fs.readFile(result.configPath as string, "utf8"); + expect(configContent).toContain('custom_rulesets:'); + expect(configContent).toContain('custom-rules/my-rule-pmd-rules.xml'); + expect(configContent).not.toContain(tempDir); + }); + + it("does not rewrite config when ruleset path already exists", async () => { + const { CreateXpathCustomRuleActionImpl } = await import("../../src/actions/create-xpath-custom-rule.js"); + const action = new CreateXpathCustomRuleActionImpl(); + tempDir = await createTempDir(); + + const configPath = path.join(tempDir, "code-analyzer.yml"); + await fs.writeFile( + configPath, + [ + "engines:", + " pmd:", + " custom_rulesets:", + ' - "custom-rules/dup-rule-pmd-rules.xml"' + ].join("\n"), + "utf8" + ); + + const result = await action.exec({ + xpath: "//MethodCallExpression[@FullMethodName='System.debug']", + ruleName: "Dup Rule", + engine: "pmd", + workingDirectory: tempDir + }); + expect(result.status).toBe("success"); + + const updated = await fs.readFile(configPath, "utf8"); + expect(countOccurrences(updated, 'custom-rules/dup-rule-pmd-rules.xml')).toBe(1); + }); + + it("adds a ruleset path to an existing config and avoids duplicates", async () => { + const { CreateXpathCustomRuleActionImpl } = await import("../../src/actions/create-xpath-custom-rule.js"); + const action = new CreateXpathCustomRuleActionImpl(); + tempDir = await createTempDir(); + + const configPath = path.join(tempDir, "code-analyzer.yml"); + await fs.writeFile( + configPath, + [ + "engines:", + " pmd:", + " custom_rulesets:", + ' - "custom-rules/existing.xml"' + ].join("\n"), + "utf8" + ); + + const result = await action.exec({ + xpath: "//MethodCallExpression[@FullMethodName='System.debug']", + ruleName: "Extra Rule", + engine: "pmd", + workingDirectory: tempDir + }); + expect(result.status).toBe("success"); + + const updated = await fs.readFile(configPath, "utf8"); + expect(updated).toContain('custom-rules/existing.xml'); + expect(updated).toContain('custom-rules/extra-rule-pmd-rules.xml'); + + const count = countOccurrences(updated, 'custom-rules/extra-rule-pmd-rules.xml'); + expect(count).toBe(1); + }); + + it("appends an engine block when missing", async () => { + const { CreateXpathCustomRuleActionImpl } = await import("../../src/actions/create-xpath-custom-rule.js"); + const action = new CreateXpathCustomRuleActionImpl(); + tempDir = await createTempDir(); + + const configPath = path.join(tempDir, "code-analyzer.yml"); + await fs.writeFile(configPath, "version: 1\n", "utf8"); + + const result = await action.exec({ + xpath: "//MethodCallExpression[@FullMethodName='System.debug']", + ruleName: "Rule", + engine: "pmd", + workingDirectory: tempDir + }); + expect(result.status).toBe("success"); + + const updated = await fs.readFile(configPath, "utf8"); + expect(updated).toContain("engines:"); + expect(updated).toContain(" pmd:"); + expect(updated).toContain('custom_rulesets:'); + expect(updated).toContain('custom-rules/rule-pmd-rules.xml'); + }); + + it("adds custom_rulesets under existing engine when missing", async () => { + const { CreateXpathCustomRuleActionImpl } = await import("../../src/actions/create-xpath-custom-rule.js"); + const action = new CreateXpathCustomRuleActionImpl(); + tempDir = await createTempDir(); + + const configPath = path.join(tempDir, "code-analyzer.yml"); + await fs.writeFile( + configPath, + [ + "engines:", + " pmd:", + " java_command: java" + ].join("\n"), + "utf8" + ); + + const result = await action.exec({ + xpath: "//MethodCallExpression[@FullMethodName='System.debug']", + ruleName: "Inserted", + engine: "pmd", + workingDirectory: tempDir + }); + expect(result.status).toBe("success"); + + const updated = await fs.readFile(configPath, "utf8"); + expect(updated).toContain(" pmd:"); + expect(updated).toContain(" custom_rulesets:"); + expect(updated).toContain('custom-rules/inserted-pmd-rules.xml'); + }); + + it("throws when config path is not readable", async () => { + const { CreateXpathCustomRuleActionImpl } = await import("../../src/actions/create-xpath-custom-rule.js"); + const action = new CreateXpathCustomRuleActionImpl(); + tempDir = await createTempDir(); + + const configPath = path.join(tempDir, "code-analyzer.yml"); + await fs.mkdir(configPath, { recursive: true }); + + await expect(action.exec({ + xpath: "//MethodCallExpression[@FullMethodName='System.debug']", + ruleName: "Unreadable", + engine: "pmd", + workingDirectory: tempDir + })).rejects.toThrow(); + }); +}); diff --git a/packages/mcp-provider-code-analyzer/test/actions/get-ast-nodes.test.ts b/packages/mcp-provider-code-analyzer/test/actions/get-ast-nodes.test.ts new file mode 100644 index 00000000..18e76d18 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/test/actions/get-ast-nodes.test.ts @@ -0,0 +1,135 @@ +import { describe, expect, it, vi } from "vitest"; + +const nestedIfXml = ` + + + + + + + + + + + + + + + +`.trim(); + +const hardcodedIdXml = ` + + + + + + + + + + + + + + + + + + +`.trim(); + +const generateAstXmlFromSourceMock = vi.fn() + .mockResolvedValueOnce(nestedIfXml) + .mockResolvedValueOnce(hardcodedIdXml); + +vi.mock("../../src/ast/generate-ast-xml.js", () => ({ + generateAstXmlFromSource: generateAstXmlFromSourceMock +})); + +describe("GetAstNodesActionImpl", () => { + it("generates AST nodes from sample Apex code", async () => { + const { GetAstNodesActionImpl } = await import("../../src/actions/get-ast-nodes.js"); + const action = new GetAstNodesActionImpl(); + + const input = { + sampleCode: "public with sharing class NestedIfExample {\n public static void checkDepth(Integer a, Integer b, Integer c, Integer d) {\n if (a > 0) {\n if (b > 0) {\n if (c > 0) {\n if (d > 0) {\n System.debug('Depth 4: violation');\n }\n }\n }\n }\n }\n}", + language: "apex", + engine: "pmd" + }; + + const result = await action.exec({ + code: input.sampleCode, + language: input.language + }); + + expect(generateAstXmlFromSourceMock).toHaveBeenCalledTimes(1); + expect(result.status).toBe("success"); + expect(result.nodes.length).toBeGreaterThan(0); + }); + + it("generates AST nodes for hardcoded Id example", async () => { + const { GetAstNodesActionImpl } = await import("../../src/actions/get-ast-nodes.js"); + const action = new GetAstNodesActionImpl(); + + const input = { + sampleCode: "public class HardcodedIdLengthExample {\n public void doWork() {\n // Violations to target: starts with 003 and length > 15 (18-char Id)\n String contactId = '0035g00000ABCDEFXYZ'; // 18 chars, starts with 003\n Id cId = '0039A00000ZZZZZQAA'; // 18 chars, starts with 003\n // Additional representative contexts\n if ('0032K00001ABCDEFGH'.startsWith('003')) { /* ... */ }\n String soql = 'SELECT Id FROM Contact WHERE Id = \\'0038X00001ABCDEFGH\\''; // embedded in string\n }\n}\n", + language: "apex", + engine: "pmd" + }; + + const result = await action.exec({ + code: input.sampleCode, + language: input.language + }); + + expect(result.status).toBe("success"); + expect(result.nodes.length).toBeGreaterThan(0); + }); + + it("returns an error status when the pipeline throws", async () => { + vi.resetModules(); + vi.doMock("../../src/ast/ast-node-pipeline.js", () => ({ + PmdAstNodePipeline: class { + public async run(): Promise { + throw new Error("boom"); + } + } + })); + + const { GetAstNodesActionImpl } = await import("../../src/actions/get-ast-nodes.js"); + const action = new GetAstNodesActionImpl(); + + const result = await action.exec({ + code: "class X {}", + language: "apex" + }); + + expect(result.status).toBe("boom"); + expect(result.nodes).toEqual([]); + expect(result.metadata).toEqual([]); + }); + + it("falls back to string error when non-Error is thrown", async () => { + vi.resetModules(); + vi.doMock("../../src/ast/ast-node-pipeline.js", () => ({ + PmdAstNodePipeline: class { + public async run(): Promise { + throw "boom-string"; + } + } + })); + + const { GetAstNodesActionImpl } = await import("../../src/actions/get-ast-nodes.js"); + const action = new GetAstNodesActionImpl(); + + const result = await action.exec({ + code: "class X {}", + language: "apex" + }); + + expect(result.status).toBe("boom-string"); + expect(result.nodes).toEqual([]); + expect(result.metadata).toEqual([]); + }); +}); diff --git a/packages/mcp-provider-code-analyzer/test/actions/run-analyzer.test.ts b/packages/mcp-provider-code-analyzer/test/actions/run-analyzer.test.ts index 76c1271a..74730e5d 100644 --- a/packages/mcp-provider-code-analyzer/test/actions/run-analyzer.test.ts +++ b/packages/mcp-provider-code-analyzer/test/actions/run-analyzer.test.ts @@ -27,7 +27,7 @@ const PATH_TO_SAMPLE_TARGETS: string = path.resolve(__dirname, '..', 'fixtures', const PATH_TO_COMPARISON_FILES: string = path.resolve(__dirname, '..', 'fixtures', 'comparison-files'); // TODO: FIGURE OUT A WAY TO MAKE THESE GOLD FILE TESTS MORE ROBUST AGAINST VERSION CHANGES. FOR NOW USING CONSTANT: -const PMD_VERSION: string = '7.18.0'; +const PMD_VERSION: string = '7.21.0'; describe('RunAnalyzerActionImpl', () => { it.each([ @@ -67,12 +67,12 @@ describe('RunAnalyzerActionImpl', () => { 'success' ], expectedSummary: { - total: 6, + total: 7, sev1: 0, sev2: 0, sev3: 3, sev4: 3, - sev5: 0 + sev5: 1 } }, { diff --git a/packages/mcp-provider-code-analyzer/test/ast/extract-ast-nodes.test.ts b/packages/mcp-provider-code-analyzer/test/ast/extract-ast-nodes.test.ts new file mode 100644 index 00000000..dd4a81bd --- /dev/null +++ b/packages/mcp-provider-code-analyzer/test/ast/extract-ast-nodes.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it, afterEach } from "vitest"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { extractAstNodes } from "../../src/ast/extract-ast-nodes.js"; + +let tempDir: string | undefined; + +async function createTempDir(): Promise { + return fs.mkdtemp(path.join(os.tmpdir(), "mcp-ast-")); +} + +async function cleanupTempDir(): Promise { + if (!tempDir) { + return; + } + await fs.rm(tempDir, { recursive: true, force: true }); + tempDir = undefined; +} + +describe("extractAstNodes", () => { + afterEach(async () => { + await cleanupTempDir(); + }); + + it("reads XML from a file path and parses nodes", async () => { + tempDir = await createTempDir(); + const xmlPath = path.join(tempDir, "ast.xml"); + const xml = [ + "", + " ", + " ", + " ", + "" + ].join("\n"); + + await fs.writeFile(xmlPath, xml, "utf8"); + + const nodes = extractAstNodes(xmlPath); + + expect(nodes.length).toBeGreaterThan(0); + expect(nodes[0].nodeName).toBe("CompilationUnit"); + }); +}); diff --git a/packages/mcp-provider-code-analyzer/test/ast/generate-ast-xml.test.ts b/packages/mcp-provider-code-analyzer/test/ast/generate-ast-xml.test.ts new file mode 100644 index 00000000..780ad32e --- /dev/null +++ b/packages/mcp-provider-code-analyzer/test/ast/generate-ast-xml.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it, vi } from "vitest"; + +const generateAstXmlMock = vi.fn(); + +vi.mock("../../src/ast/pmd-engine-adapter.js", () => ({ + PmdEngineAstXmlAdapter: class { + constructor(_pmdEngine: any) {} + public generateAstXml(code: string, language: string): Promise { + return generateAstXmlMock(code, language); + } + } +})); + +vi.mock("@salesforce/code-analyzer-pmd-engine", () => ({ + PmdEngine: class { + constructor(_config: any) {} + } +})); + +describe("generateAstXmlFromSource", () => { + it("delegates to the PMD Engine adapter", async () => { + generateAstXmlMock.mockResolvedValueOnce(""); + const { generateAstXmlFromSource } = await import("../../src/ast/generate-ast-xml.js"); + + const result = await generateAstXmlFromSource("class X {}", "apex"); + + expect(generateAstXmlMock).toHaveBeenCalledTimes(1); + expect(generateAstXmlMock).toHaveBeenCalledWith("class X {}", "apex"); + expect(result).toBe(""); + }); + + it("propagates adapter errors", async () => { + generateAstXmlMock.mockRejectedValueOnce(new Error("boom")); + const { generateAstXmlFromSource } = await import("../../src/ast/generate-ast-xml.js"); + + await expect(generateAstXmlFromSource("class X {}", "apex")).rejects.toThrow("boom"); + }); +}); diff --git a/packages/mcp-provider-code-analyzer/test/e2e/run_code_analyzer-e2e.test.ts b/packages/mcp-provider-code-analyzer/test/e2e/run_code_analyzer-e2e.test.ts index 4d599106..b4b57caa 100644 --- a/packages/mcp-provider-code-analyzer/test/e2e/run_code_analyzer-e2e.test.ts +++ b/packages/mcp-provider-code-analyzer/test/e2e/run_code_analyzer-e2e.test.ts @@ -39,7 +39,7 @@ describe('run_code_analyzer', () => { { case: 'violations are present', target: path.join(__dirname, '..', 'fixtures', 'sample-targets', 'ApexTarget2.cls'), - expectedCount: 6 + expectedCount: 7 }, { case: 'no violations are present', diff --git a/packages/mcp-provider-code-analyzer/test/engines/engine-strategies.test.ts b/packages/mcp-provider-code-analyzer/test/engines/engine-strategies.test.ts new file mode 100644 index 00000000..be4656cc --- /dev/null +++ b/packages/mcp-provider-code-analyzer/test/engines/engine-strategies.test.ts @@ -0,0 +1,106 @@ +import { describe, expect, it, vi } from "vitest"; + +const generateAstXmlFromSourceMock = vi.fn(); +const getApexAstNodeMetadataByNamesMock = vi.fn(); + +vi.mock("../../src/ast/generate-ast-xml.js", () => ({ + generateAstXmlFromSource: generateAstXmlFromSourceMock +})); + +vi.mock("../../src/ast/metadata/apex-ast-reference.js", () => ({ + getApexAstNodeMetadataByNames: getApexAstNodeMetadataByNamesMock +})); + +describe("engine strategies", () => { + it("returns PMD strategy and generates AST XML via generator", async () => { + generateAstXmlFromSourceMock.mockResolvedValueOnce(""); + const { getEngineStrategy } = await import("../../src/engines/engine-strategies.js"); + const strategy = getEngineStrategy("pmd"); + + const result = await strategy.astGenerator.generateAstXml("class X {}", "apex"); + + expect(result).toBe(""); + expect(generateAstXmlFromSourceMock).toHaveBeenCalledWith("class X {}", "apex"); + }); + + it("returns metadata only for Apex language", async () => { + getApexAstNodeMetadataByNamesMock.mockResolvedValueOnce([{ name: "UserClass" }]); + const { getEngineStrategy } = await import("../../src/engines/engine-strategies.js"); + const strategy = getEngineStrategy("pmd"); + + const apexMeta = await strategy.metadataProvider.getMetadata("apex", ["UserClass"]); + const jsMeta = await strategy.metadataProvider.getMetadata("javascript", ["Foo"]); + + expect(apexMeta).toEqual([{ name: "UserClass" }]); + expect(jsMeta).toEqual([]); + }); + + it("treats missing language as non-Apex for metadata", async () => { + const { getEngineStrategy } = await import("../../src/engines/engine-strategies.js"); + const strategy = getEngineStrategy("pmd"); + + const meta = await strategy.metadataProvider.getMetadata(undefined as unknown as string, ["UserClass"]); + + expect(meta).toEqual([]); + }); + + it("builds a PMD prompt with AST node summaries", async () => { + const { getEngineStrategy } = await import("../../src/engines/engine-strategies.js"); + const strategy = getEngineStrategy("pmd"); + + const prompt = strategy.promptBuilder.buildPrompt({ + language: "apex", + engine: "pmd", + astNodes: [ + { + nodeName: "MethodCallExpression", + attributes: { FullMethodName: "System.debug" }, + parent: "BlockStatement", + ancestors: ["CompilationUnit", "UserClass"] + } + ], + astMetadata: [ + { + name: "MethodCallExpression", + description: "Represents a method call" + } + ] + }); + + expect(prompt).toContain("You are generating a PMD XPath query."); + expect(prompt).toContain("MethodCallExpression"); + expect(prompt).toContain("System.debug"); + expect(prompt).toContain("Create the XPath"); + }); + + it("includes null parent when AST node has no parent", async () => { + const { getEngineStrategy } = await import("../../src/engines/engine-strategies.js"); + const strategy = getEngineStrategy("pmd"); + + const prompt = strategy.promptBuilder.buildPrompt({ + language: "apex", + engine: "pmd", + astNodes: [ + { + nodeName: "CompilationUnit", + attributes: {}, + parent: undefined, + ancestors: [] + } + ], + astMetadata: [] + }); + + expect(prompt).toContain('"parent": null'); + }); + + it("throws for unsupported engines", async () => { + const { getEngineStrategy } = await import("../../src/engines/engine-strategies.js"); + expect(() => getEngineStrategy("eslint")).toThrow("engine 'eslint' is not supported yet"); + }); + + it("throws for empty engine value", async () => { + const { getEngineStrategy } = await import("../../src/engines/engine-strategies.js"); + expect(() => getEngineStrategy("")).toThrow("engine '' is not supported yet"); + }); +}); diff --git a/packages/mcp-provider-code-analyzer/test/fixtures/comparison-files/violations-in-ApexTarget2-cls.goldfile.json b/packages/mcp-provider-code-analyzer/test/fixtures/comparison-files/violations-in-ApexTarget2-cls.goldfile.json index 3884ff07..35d8d77b 100644 --- a/packages/mcp-provider-code-analyzer/test/fixtures/comparison-files/violations-in-ApexTarget2-cls.goldfile.json +++ b/packages/mcp-provider-code-analyzer/test/fixtures/comparison-files/violations-in-ApexTarget2-cls.goldfile.json @@ -1,12 +1,12 @@ { "runDir": "{{RUNDIR}}{{SEP}}", "violationCounts": { - "total": 6, + "total": 7, "sev1": 0, "sev2": 0, "sev3": 3, "sev4": 3, - "sev5": 0 + "sev5": 1 }, "versions": { "code-analyzer": "{{CODE_ANALYZER_VERSION}}", @@ -157,6 +157,28 @@ "resources": [ "https://docs.pmd-code.org/pmd-doc-{{PMD_VERSION}}/pmd_rules_apex_performance.html#operationwithlimitsinloop" ] + }, + { + "rule": "NoTrailingWhitespace", + "engine": "regex", + "severity": 5, + "tags": [ + "Recommended", + "CodeStyle", + "Apex" + ], + "primaryLocationIndex": 0, + "locations": [ + { + "file": "test{{SEP}}fixtures{{SEP}}sample-targets{{SEP}}ApexTarget2.cls", + "startLine": 3, + "startColumn": 1, + "endLine": 4, + "endColumn": 1 + } + ], + "message": "Found trailing whitespace at the end of a line of code.", + "resources": [] } ] } \ No newline at end of file diff --git a/packages/mcp-provider-code-analyzer/test/provider.test.ts b/packages/mcp-provider-code-analyzer/test/provider.test.ts index efd4cfc7..2a79b493 100644 --- a/packages/mcp-provider-code-analyzer/test/provider.test.ts +++ b/packages/mcp-provider-code-analyzer/test/provider.test.ts @@ -5,6 +5,8 @@ import { StubServices } from "./test-doubles.js"; import { CodeAnalyzerDescribeRuleMcpTool } from "../src/tools/describe_code_analyzer_rule.js"; import { CodeAnalyzerListRulesMcpTool } from "../src/tools/list_code_analyzer_rules.js"; import { CodeAnalyzerQueryResultsMcpTool } from "../src/tools/query_code_analyzer_results.js"; +import { GenerateXpathPromptMcpTool } from "../src/tools/generate_xpath_prompt.js"; +import { CreateCustomRuleMcpTool } from "../src/tools/create_custom_rule.js"; describe("Tests for CodeAnalyzerMcpProvider", () => { let services: Services; @@ -21,10 +23,12 @@ describe("Tests for CodeAnalyzerMcpProvider", () => { it("When provideTools is called, then the returned array contains an CodeAnalyzerRunMcpTool instance", async () => { const tools: McpTool[] = await provider.provideTools(services); - expect(tools).toHaveLength(4); + expect(tools).toHaveLength(6); expect(tools[0]).toBeInstanceOf(CodeAnalyzerRunMcpTool); expect(tools[1]).toBeInstanceOf(CodeAnalyzerDescribeRuleMcpTool); expect(tools[2]).toBeInstanceOf(CodeAnalyzerListRulesMcpTool); expect(tools[3]).toBeInstanceOf(CodeAnalyzerQueryResultsMcpTool); + expect(tools[4]).toBeInstanceOf(GenerateXpathPromptMcpTool); + expect(tools[5]).toBeInstanceOf(CreateCustomRuleMcpTool); }); }) \ No newline at end of file diff --git a/packages/mcp-provider-code-analyzer/test/tools/create_custom_rule.test.ts b/packages/mcp-provider-code-analyzer/test/tools/create_custom_rule.test.ts new file mode 100644 index 00000000..6a1332fa --- /dev/null +++ b/packages/mcp-provider-code-analyzer/test/tools/create_custom_rule.test.ts @@ -0,0 +1,205 @@ +import { describe, expect, it, vi } from "vitest"; +import { CreateCustomRuleMcpTool } from "../../src/tools/create_custom_rule.js"; + +const actionExecMock = vi.fn(); +const telemetrySendMock = vi.fn(); + +function buildTool() { + return new CreateCustomRuleMcpTool( + { + exec: actionExecMock + }, + { + sendEvent: telemetrySendMock + } + ); +} + +describe("CreateCustomRuleMcpTool", () => { + it("exposes the expected name and config", () => { + const tool = buildTool(); + const config = tool.getConfig(); + + expect(tool.getName()).toBe("create_custom_rule"); + expect(tool.getReleaseState()).toBe("non-ga"); + expect(tool.getToolsets()).toEqual(["code-analysis"]); + expect(config.title).toBe("Create Custom Rule"); + expect(config.description).toContain("Purpose: Create a custom rule"); + }); + + it("returns validation error when ruleName is missing", async () => { + const tool = buildTool(); + const result = await tool.exec({ + xpath: "//MethodCallExpression", + ruleName: "", + description: "desc", + language: "apex", + engine: "pmd", + priority: 3, + workingDirectory: "/tmp" + }); + + expect(result.structuredContent?.status).toContain("ruleName is required"); + expect(actionExecMock).not.toHaveBeenCalled(); + }); + + it("returns validation error when description is missing", async () => { + const tool = buildTool(); + const result = await tool.exec({ + xpath: "//MethodCallExpression", + ruleName: "Rule", + description: " ", + language: "apex", + engine: "pmd", + priority: 3, + workingDirectory: "/tmp" + }); + + expect(result.structuredContent?.status).toContain("description is required"); + expect(actionExecMock).not.toHaveBeenCalled(); + }); + + it("returns validation error when language is missing", async () => { + const tool = buildTool(); + const result = await tool.exec({ + xpath: "//MethodCallExpression", + ruleName: "Rule", + description: "desc", + language: " ", + engine: "pmd", + priority: 3, + workingDirectory: "/tmp" + }); + + expect(result.structuredContent?.status).toContain("language is required"); + expect(actionExecMock).not.toHaveBeenCalled(); + }); + + it("returns validation error when engine is missing", async () => { + const tool = buildTool(); + const result = await tool.exec({ + xpath: "//MethodCallExpression", + ruleName: "Rule", + description: "desc", + language: "apex", + engine: " ", + priority: 3, + workingDirectory: "/tmp" + }); + + expect(result.structuredContent?.status).toContain("engine is required"); + expect(actionExecMock).not.toHaveBeenCalled(); + }); + + it("returns validation error when xpath is missing for PMD", async () => { + const tool = buildTool(); + const result = await tool.exec({ + xpath: " ", + ruleName: "Rule", + description: "desc", + language: "apex", + engine: "pmd", + priority: 3, + workingDirectory: "/tmp" + }); + + expect(result.structuredContent?.status).toContain("xpath is required for engine 'pmd'"); + expect(actionExecMock).not.toHaveBeenCalled(); + }); + + it("allows missing xpath for non-PMD engines", async () => { + actionExecMock.mockResolvedValueOnce({ + status: "success", + rulesetPath: "/tmp/custom.xml", + configPath: "/tmp/code-analyzer.yml" + }); + + const tool = buildTool(); + await tool.exec({ + xpath: " ", + ruleName: "Rule", + description: "desc", + language: "apex", + engine: "eslint", + priority: 3, + workingDirectory: "/tmp" + }); + + expect(actionExecMock).toHaveBeenCalledTimes(1); + }); + + it("returns validation error when priority is missing", async () => { + const tool = buildTool(); + const result = await tool.exec({ + xpath: "//MethodCallExpression", + ruleName: "Rule", + description: "desc", + language: "apex", + engine: "pmd", + priority: undefined, + workingDirectory: "/tmp" + } as unknown as Parameters[0]); + + expect(result.structuredContent?.status).toContain("priority is required"); + expect(actionExecMock).not.toHaveBeenCalled(); + }); + + it("returns validation error when workingDirectory is missing", async () => { + const tool = buildTool(); + const result = await tool.exec({ + xpath: "//MethodCallExpression", + ruleName: "Rule", + description: "desc", + language: "apex", + engine: "pmd", + priority: 3, + workingDirectory: " " + }); + + expect(result.structuredContent?.status).toContain("workingDirectory is required"); + expect(actionExecMock).not.toHaveBeenCalled(); + }); + + it("delegates to action and emits telemetry on success", async () => { + actionExecMock.mockResolvedValueOnce({ + status: "success", + rulesetPath: "/tmp/custom.xml", + configPath: "/tmp/code-analyzer.yml" + }); + + const tool = buildTool(); + const result = await tool.exec({ + xpath: "//MethodCallExpression", + ruleName: "Rule", + description: "desc", + language: "apex", + engine: "pmd", + priority: 3, + workingDirectory: "/tmp" + }); + + expect(actionExecMock).toHaveBeenCalledTimes(1); + expect(result.content?.[0]?.text).toContain("Custom rule created"); + expect(telemetrySendMock).toHaveBeenCalledTimes(1); + }); + + it("does not emit telemetry on failure", async () => { + actionExecMock.mockResolvedValueOnce({ + status: "something failed" + }); + + const tool = buildTool(); + const result = await tool.exec({ + xpath: "//MethodCallExpression", + ruleName: "Rule", + description: "desc", + language: "apex", + engine: "pmd", + priority: 3, + workingDirectory: "/tmp" + }); + + expect(result.content?.[0]?.text).toBe("something failed"); + expect(telemetrySendMock).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/mcp-provider-code-analyzer/test/tools/generate_xpath_prompt.test.ts b/packages/mcp-provider-code-analyzer/test/tools/generate_xpath_prompt.test.ts new file mode 100644 index 00000000..47ab39dd --- /dev/null +++ b/packages/mcp-provider-code-analyzer/test/tools/generate_xpath_prompt.test.ts @@ -0,0 +1,147 @@ +import { describe, expect, it, vi, beforeEach } from "vitest"; + +const actionExecMock = vi.fn(); +const telemetrySendMock = vi.fn(); +const buildPromptMock = vi.fn(); + +vi.mock("../../src/engines/engine-strategies.js", () => ({ + getEngineStrategy: () => ({ + promptBuilder: { + buildPrompt: buildPromptMock + } + }) +})); + +beforeEach(() => { + vi.clearAllMocks(); + vi.resetModules(); +}); + +async function buildTool(withTelemetry: boolean = true) { + const { GenerateXpathPromptMcpTool } = await import("../../src/tools/generate_xpath_prompt.js"); + return new GenerateXpathPromptMcpTool( + { + exec: actionExecMock + }, + withTelemetry + ? { sendEvent: telemetrySendMock } + : undefined + ); +} + +describe("GenerateXpathPromptMcpTool", () => { + it("returns validation error when language is missing", async () => { + const tool = await buildTool(); + const result = await tool.exec({ + sampleCode: "class X {}", + language: " ", + engine: "pmd" + }); + + expect(result.structuredContent?.status).toBe("language is required"); + expect(actionExecMock).not.toHaveBeenCalled(); + }); + + it("returns validation error when engine is missing", async () => { + const tool = await buildTool(); + const result = await tool.exec({ + sampleCode: "class X {}", + language: "apex", + engine: " " + }); + + expect(result.structuredContent?.status).toBe("engine is required"); + expect(actionExecMock).not.toHaveBeenCalled(); + }); + + it("returns validation error for unsupported engine", async () => { + const tool = await buildTool(); + const result = await tool.exec({ + sampleCode: "class X {}", + language: "apex", + engine: "eslint" + }); + + expect(result.structuredContent?.status).toBe("engine 'eslint' is not supported yet"); + expect(actionExecMock).not.toHaveBeenCalled(); + }); + + it("returns validation error when sample code is missing", async () => { + const tool = await buildTool(); + const result = await tool.exec({ + sampleCode: " ", + language: "apex", + engine: "pmd" + }); + + expect(result.structuredContent?.status).toBe("code in apex is required"); + expect(actionExecMock).not.toHaveBeenCalled(); + }); + + it("returns error when AST action fails", async () => { + actionExecMock.mockResolvedValueOnce({ + status: "failure", + nodes: [], + metadata: [] + }); + + const tool = await buildTool(); + const result = await tool.exec({ + sampleCode: "class X {}", + language: "apex", + engine: "pmd" + }); + + expect(result.structuredContent?.status).toBe("failure"); + expect(result.structuredContent?.prompt).toBe(""); + }); + + it("builds a prompt and emits telemetry on success", async () => { + actionExecMock.mockResolvedValueOnce({ + status: "success", + nodes: [{ nodeName: "CompilationUnit", attributes: {}, ancestors: [] }], + metadata: [] + }); + buildPromptMock.mockReturnValueOnce("PROMPT"); + + const tool = await buildTool(); + const result = await tool.exec({ + sampleCode: "class X {}", + language: "apex", + engine: "pmd" + }); + + expect(buildPromptMock).toHaveBeenCalledTimes(1); + expect(result.structuredContent?.prompt).toBe("PROMPT"); + expect(telemetrySendMock).toHaveBeenCalledTimes(1); + }); + + it("does not emit telemetry when telemetry service is not provided", async () => { + actionExecMock.mockResolvedValueOnce({ + status: "success", + nodes: [], + metadata: [] + }); + buildPromptMock.mockReturnValueOnce("PROMPT"); + + const tool = await buildTool(false); + await tool.exec({ + sampleCode: "class X {}", + language: "apex", + engine: "pmd" + }); + + expect(telemetrySendMock).not.toHaveBeenCalled(); + }); + + it("exposes name, toolsets, release state, and config", async () => { + const tool = await buildTool(); + const config = tool.getConfig(); + + expect(tool.getName()).toBe("get_ast_nodes_to_generate_xpath"); + expect(tool.getReleaseState()).toBe("non-ga"); + expect(tool.getToolsets()).toEqual(["code-analysis"]); + expect(config.title).toBe("Generate XPath Prompt"); + expect(config.description).toContain("First step for creating a PMD XPath-based custom rule"); + }); +}); diff --git a/packages/mcp-provider-code-analyzer/test/utils.test.ts b/packages/mcp-provider-code-analyzer/test/utils.test.ts new file mode 100644 index 00000000..05457796 --- /dev/null +++ b/packages/mcp-provider-code-analyzer/test/utils.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; +import { escapeXml, toSafeFilenameSlug } from "../src/utils.js"; + +describe("utils", () => { + describe("escapeXml", () => { + it("escapes XML special characters", () => { + expect(escapeXml(`Tom & "Jerry" 's`)) + .toBe("Tom & "Jerry" <tag>'s</tag>"); + }); + }); + + describe("toSafeFilenameSlug", () => { + it("normalizes spaces and invalid characters", () => { + expect(toSafeFilenameSlug(" My Rule:Name ")).toBe("my-rule-name"); + }); + + it("removes path separators and collapses dashes", () => { + expect(toSafeFilenameSlug("foo/bar\\baz---qux")).toBe("foo-bar-baz-qux"); + }); + + it("strips dots to prevent path traversal", () => { + expect(toSafeFilenameSlug("../etc/passwd")).toBe("etc-passwd"); + expect(toSafeFilenameSlug("..")).toBe("custom-rule"); + }); + + it("falls back to a default when empty", () => { + expect(toSafeFilenameSlug(" ")).toBe("custom-rule"); + }); + }); +}); diff --git a/packages/mcp-provider-dx-core/package.json b/packages/mcp-provider-dx-core/package.json index bd121210..c969079d 100644 --- a/packages/mcp-provider-dx-core/package.json +++ b/packages/mcp-provider-dx-core/package.json @@ -9,6 +9,7 @@ "build": "wireit", "build:watch": "yarn build --watch", "clean": "tsc --build --clean", + "clean-all": "yarn clean && rimraf node_modules", "format": "wireit", "lint": "wireit", "test": "wireit", diff --git a/packages/mcp-provider-dx-core/test/e2e/dynamic-tools.test.ts b/packages/mcp-provider-dx-core/test/e2e/dynamic-tools.test.ts index d5c542dc..7a5e6c2d 100644 --- a/packages/mcp-provider-dx-core/test/e2e/dynamic-tools.test.ts +++ b/packages/mcp-provider-dx-core/test/e2e/dynamic-tools.test.ts @@ -19,13 +19,16 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { DxMcpTransport } from '@salesforce/mcp-test-client'; import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; -describe('sf-dynamic-tools', () => { +describe('sf-dynamic-tools', function() { + this.timeout(60000); // Set 60 second timeout for all tests in this suite + const client = new Client({ name: 'sf-dynamic-tools', version: '0.0.1', }); - before(async () => { + before(async function() { + this.timeout(60000); // Set 60 second timeout for before hook const transport = DxMcpTransport({ args: ['--orgs', 'ALLOW_ALL_ORGS', '--dynamic-tools', '--no-telemetry'], }); @@ -33,11 +36,13 @@ describe('sf-dynamic-tools', () => { await client.connect(transport); }); - after(async () => { + after(async function() { + this.timeout(60000); // Set 60 second timeout for after hook await client.close(); }); - it('should enable 2 tools', async () => { + it('should enable 2 tools', async function() { + this.timeout(60000); // Set 60 second timeout for this test const initialTools = (await client.listTools()).tools.map((t) => t.name).sort(); expect(initialTools.length).to.equal(4); @@ -70,7 +75,8 @@ describe('sf-dynamic-tools', () => { ); }); - it('should list available tools to be enabled', async () => { + it('should list available tools to be enabled', async function() { + this.timeout(60000); // Set 60 second timeout for this test const result = (await client.callTool({ name: 'list_tools', })) as CallToolResult; diff --git a/packages/mcp-provider-dx-core/test/e2e/tool-registration.test.ts b/packages/mcp-provider-dx-core/test/e2e/tool-registration.test.ts index ac12b86f..338d44ac 100644 --- a/packages/mcp-provider-dx-core/test/e2e/tool-registration.test.ts +++ b/packages/mcp-provider-dx-core/test/e2e/tool-registration.test.ts @@ -33,8 +33,11 @@ async function getMcpClient(opts: { args: string[] }) { return client; } -describe('specific tool registration', () => { - it('should enable 2 tools', async () => { +describe('specific tool registration', function() { + this.timeout(60000); // Set 60 second timeout for all tests in this suite + + it('should enable 2 tools', async function() { + this.timeout(60000); // Set 60 second timeout for this test const client = await getMcpClient({ args: ['--orgs', 'ALLOW_ALL_ORGS', '--tools', 'run_soql_query, deploy_metadata', '--no-telemetry'], }); @@ -54,7 +57,8 @@ describe('specific tool registration', () => { } }); - it('should not enable NON_GA tools if --allow-non-ga-tools is not specified', async () => { + it('should not enable NON_GA tools if --allow-non-ga-tools is not specified', async function() { + this.timeout(60000); // Set 60 second timeout for this test const client = await getMcpClient({ args: ['--orgs', 'ALLOW_ALL_ORGS', '--tools', 'run_soql_query, list_devops_center_work_items', '--no-telemetry'], }); @@ -73,7 +77,8 @@ describe('specific tool registration', () => { } }); - it('should enable 1 tool and a toolset', async () => { + it('should enable 1 tool and a toolset', async function() { + this.timeout(60000); // Set 60 second timeout for this test const client = await getMcpClient({ args: [ '--orgs', @@ -90,7 +95,7 @@ describe('specific tool registration', () => { try { const initialTools = (await client.listTools()).tools.map((t) => t.name).sort(); - expect(initialTools.length).to.equal(7); + expect(initialTools.length).to.equal(9); expect(initialTools).to.deep.equal( [ 'run_soql_query', @@ -100,6 +105,8 @@ describe('specific tool registration', () => { 'run_code_analyzer', 'list_code_analyzer_rules', 'query_code_analyzer_results', + 'get_ast_nodes_to_generate_xpath', + 'create_custom_rule', ].sort(), ); } catch (err) { diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 13bfabdb..3f3347bf 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -12,6 +12,7 @@ "build": "wireit", "build:watch": "yarn build --watch", "clean": "tsc --build --clean", + "clean-all": "yarn clean && rimraf node_modules", "fix-license": "eslint src test --fix --rule \"header/header: [2]\"", "format": "wireit", "link-check": "wireit", diff --git a/yarn.lock b/yarn.lock index 86253695..520ad595 100644 --- a/yarn.lock +++ b/yarn.lock @@ -719,11 +719,25 @@ js-tokens "^4.0.0" picocolors "^1.1.1" +"@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" + integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== + dependencies: + "@babel/helper-validator-identifier" "^7.28.5" + js-tokens "^4.0.0" + picocolors "^1.1.1" + "@babel/compat-data@^7.27.2": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.4.tgz#96fdf1af1b8859c8474ab39c295312bfb7c24b04" integrity sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw== +"@babel/compat-data@^7.28.6": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d" + integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== + "@babel/core@7.27.4": version "7.27.4" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.4.tgz#cc1fc55d0ce140a1828d1dd2a2eba285adbfb3ce" @@ -766,6 +780,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.24.4": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322" + integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/core@~7.24.7": version "7.24.9" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.9.tgz#dc07c9d307162c97fa9484ea997ade65841c7c82" @@ -855,6 +890,24 @@ "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" +"@babel/generator@^7.29.0": + version "7.29.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50" + integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw== + dependencies: + "@babel/parser" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3": + version "7.27.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" + integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== + dependencies: + "@babel/types" "^7.27.3" + "@babel/helper-compilation-targets@^7.24.8", "@babel/helper-compilation-targets@^7.26.5", "@babel/helper-compilation-targets@^7.27.2": version "7.27.2" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" @@ -866,6 +919,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== + dependencies: + "@babel/compat-data" "^7.28.6" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-globals@^7.28.0": version "7.28.0" resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" @@ -879,6 +943,14 @@ "@babel/traverse" "^7.27.1" "@babel/types" "^7.27.1" +"@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== + dependencies: + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + "@babel/helper-module-transforms@^7.24.9", "@babel/helper-module-transforms@^7.26.0", "@babel/helper-module-transforms@^7.27.3", "@babel/helper-module-transforms@^7.28.3": version "7.28.3" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" @@ -888,6 +960,20 @@ "@babel/helper-validator-identifier" "^7.27.1" "@babel/traverse" "^7.28.3" +"@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== + dependencies: + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" + +"@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8" + integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== + "@babel/helper-string-parser@^7.25.9", "@babel/helper-string-parser@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" @@ -911,6 +997,14 @@ "@babel/template" "^7.27.2" "@babel/types" "^7.28.4" +"@babel/helpers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.6.tgz#fca903a313ae675617936e8998b814c415cbf5d7" + integrity sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw== + dependencies: + "@babel/template" "^7.28.6" + "@babel/types" "^7.28.6" + "@babel/parser@^7.23.9", "@babel/parser@^7.24.8", "@babel/parser@^7.25.4", "@babel/parser@^7.25.9", "@babel/parser@^7.26.10", "@babel/parser@^7.27.2", "@babel/parser@^7.27.4", "@babel/parser@^7.28.5", "@babel/parser@^7.9.0": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.5.tgz#0b0225ee90362f030efd644e8034c99468893b08" @@ -918,6 +1012,13 @@ dependencies: "@babel/types" "^7.28.5" +"@babel/parser@^7.24.4", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.0.tgz#669ef345add7d057e92b7ed15f0bac07611831b6" + integrity sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww== + dependencies: + "@babel/types" "^7.29.0" + "@babel/parser@~7.26.2": version "7.26.10" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749" @@ -925,6 +1026,58 @@ dependencies: "@babel/types" "^7.26.10" +"@babel/plugin-syntax-jsx@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee" + integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-react-display-name@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz#6f20a7295fea7df42eb42fed8f896813f5b934de" + integrity sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-jsx-development@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz#47ff95940e20a3a70e68ad3d4fcb657b647f6c98" + integrity sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.27.1" + +"@babel/plugin-transform-react-jsx@^7.27.1": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz#f51cb70a90b9529fbb71ee1f75ea27b7078eed62" + integrity sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-syntax-jsx" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/plugin-transform-react-pure-annotations@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz#339f1ce355eae242e0649f232b1c68907c02e879" + integrity sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/preset-react@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.28.5.tgz#6fcc0400fa79698433d653092c3919bb4b0878d9" + integrity sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-validator-option" "^7.27.1" + "@babel/plugin-transform-react-display-name" "^7.28.0" + "@babel/plugin-transform-react-jsx" "^7.27.1" + "@babel/plugin-transform-react-jsx-development" "^7.27.1" + "@babel/plugin-transform-react-pure-annotations" "^7.27.1" + "@babel/runtime-corejs3@^7.12.5": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz#c25be39c7997ce2f130d70b9baecb8ed94df93fa" @@ -946,6 +1099,15 @@ "@babel/parser" "^7.27.2" "@babel/types" "^7.27.1" +"@babel/template@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + "@babel/traverse@^7.24.8", "@babel/traverse@^7.26.10", "@babel/traverse@^7.27.1", "@babel/traverse@^7.27.4", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.5.tgz#450cab9135d21a7a2ca9d2d35aa05c20e68c360b" @@ -959,6 +1121,19 @@ "@babel/types" "^7.28.5" debug "^4.3.1" +"@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" + integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + debug "^4.3.1" + "@babel/traverse@~7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" @@ -980,6 +1155,14 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" +"@babel/types@^7.28.6", "@babel/types@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/types@~7.26.0": version "7.26.10" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259" @@ -1344,11 +1527,23 @@ dependencies: eslint-visitor-keys "^3.4.3" +"@eslint-community/eslint-utils@^4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== + dependencies: + eslint-visitor-keys "^3.4.3" + "@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1", "@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": version "4.12.1" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== +"@eslint-community/regexpp@^4.12.2": + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + "@eslint/config-array@^0.21.1": version "0.21.1" resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.1.tgz#7d1b0060fea407f8301e932492ba8c18aff29713" @@ -1358,6 +1553,15 @@ debug "^4.3.1" minimatch "^3.1.2" +"@eslint/config-array@^0.21.2": + version "0.21.2" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.2.tgz#f29e22057ad5316cf23836cee9a34c81fffcb7e6" + integrity sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw== + dependencies: + "@eslint/object-schema" "^2.1.7" + debug "^4.3.1" + minimatch "^3.1.5" + "@eslint/config-helpers@^0.4.2": version "0.4.2" resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz#1bd006ceeb7e2e55b2b773ab318d300e1a66aeda" @@ -1386,7 +1590,7 @@ dependencies: "@types/json-schema" "^7.0.15" -"@eslint/css-tree@^3.6.1", "@eslint/css-tree@^3.6.5", "@eslint/css-tree@^3.6.6": +"@eslint/css-tree@^3.6.1", "@eslint/css-tree@^3.6.5": version "3.6.8" resolved "https://registry.yarnpkg.com/@eslint/css-tree/-/css-tree-3.6.8.tgz#8449d80a6e061bde514bd302a278a533d9716aba" integrity sha512-s0f40zY7dlMp8i0Jf0u6l/aSswS0WRAgkhgETgiCJRcxIWb4S/Sp9uScKHWbkM3BnoFLbJbmOYk5AZUDFVxaLA== @@ -1394,15 +1598,6 @@ mdn-data "2.23.0" source-map-js "^1.0.1" -"@eslint/css@^0.14.1": - version "0.14.1" - resolved "https://registry.yarnpkg.com/@eslint/css/-/css-0.14.1.tgz#ca80b9c21b5de901e173ba8180f9cd8879315604" - integrity sha512-NXiteSacmpaXqgyIW3+GcNzexXyfC0kd+gig6WTjD4A74kBGJeNx1tV0Hxa0v7x0+mnIyKfGPhGNs1uhRFdh+w== - dependencies: - "@eslint/core" "^0.17.0" - "@eslint/css-tree" "^3.6.6" - "@eslint/plugin-kit" "^0.4.1" - "@eslint/css@^0.9.0": version "0.9.0" resolved "https://registry.yarnpkg.com/@eslint/css/-/css-0.9.0.tgz#fc095bb0a0ec5da5c7482d6360a64ba93743575b" @@ -1442,6 +1637,21 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@eslint/eslintrc@^3.3.5": + version "3.3.5" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.5.tgz#c131793cfc1a7b96f24a83e0a8bbd4b881558c60" + integrity sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg== + dependencies: + ajv "^6.14.0" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.1" + minimatch "^3.1.5" + strip-json-comments "^3.1.1" + "@eslint/js@8.57.1": version "8.57.1" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" @@ -1452,6 +1662,11 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.1.tgz#0dd59c3a9f40e3f1882975c321470969243e0164" integrity sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw== +"@eslint/js@9.39.4", "@eslint/js@^9.39.2": + version "9.39.4" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.4.tgz#a3f83bfc6fd9bf33a853dfacd0b49b398eb596c1" + integrity sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw== + "@eslint/object-schema@^2.1.7": version "2.1.7" resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad" @@ -2815,19 +3030,19 @@ resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== -"@salesforce-ux/eslint-plugin-slds@^1.0.6": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@salesforce-ux/eslint-plugin-slds/-/eslint-plugin-slds-1.0.6.tgz#6a95fc9679a5a618f4d2a6d6d95c6fa39141c345" - integrity sha512-rjT8qd1S7r7ZVgAZ6xsUxBv4o/c44cX+miQrguh6ME06kEE8AiExKXg/3BlUjlyzBGMHwm/QHbipMzqt1zRAuA== +"@salesforce-ux/eslint-plugin-slds@^1.1.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@salesforce-ux/eslint-plugin-slds/-/eslint-plugin-slds-1.2.1.tgz#b5712136e45635dd4f309966e1a76ee1bb2fb7d1" + integrity sha512-DeByr0oCNnKKnDKOhHt2kzeOGgN7lf/WnD43y20YFMhN2kWZ/aDHMG2fRwWSuG4iBBjHXIGFb9SdJoN8+murOg== dependencies: "@eslint/css" "^0.9.0" "@eslint/css-tree" "^3.6.5" "@html-eslint/eslint-plugin" "^0.34.0" "@html-eslint/parser" "^0.34.0" - "@salesforce-ux/sds-metadata" "^1.1.0" + "@salesforce-ux/sds-metadata" "^1.2.1" chroma-js "^3.1.2" -"@salesforce-ux/sds-metadata@^1.1.0": +"@salesforce-ux/sds-metadata@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@salesforce-ux/sds-metadata/-/sds-metadata-1.2.1.tgz#37d576d2ea09e31a0beae97fdc857375f54eb59b" integrity sha512-u1QZddRZPvGEcoIzn/nxHvk/LiuyXXkl+7WpLjBd3oEpXraRCIIag3jnWJx7Q22jcz/3moVxYPx6iyqxTS7U1g== @@ -2876,61 +3091,65 @@ strip-ansi "6.0.1" ts-retry-promise "^0.8.1" -"@salesforce/code-analyzer-core@^0.40.0": - version "0.40.0" - resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-core/-/code-analyzer-core-0.40.0.tgz#d1bf616a4e30b860e16b3a41ad78865c451aa3d5" - integrity sha512-CTEAlLMk8O+HV0oow94E7Ezw6FYV/04qeXRK+oag18G1TVPPrS2aL9MrzZ8TzO0hzTSEDLqzacpLfP2DSCBCXA== +"@salesforce/code-analyzer-core@^0.43.0": + version "0.43.0" + resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-core/-/code-analyzer-core-0.43.0.tgz#d15ebc7df4fd589ad24d0ba30057dbd561caa652" + integrity sha512-Jt549hKn6iQZ77rMlx5JCk9lBWuzV8wc1Ie20Cw1dqX5XDamYzWwEiTEM+9tne3KdRDDoR4xhD29xuaJBBUqHw== dependencies: - "@salesforce/code-analyzer-engine-api" "0.32.0" + "@salesforce/code-analyzer-engine-api" "0.35.0" "@types/node" "^20.0.0" csv-stringify "^6.6.0" js-yaml "^4.1.1" - semver "^7.7.3" + semver "^7.7.4" xmlbuilder "^15.1.1" -"@salesforce/code-analyzer-engine-api@0.32.0", "@salesforce/code-analyzer-engine-api@^0.32.0": - version "0.32.0" - resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-engine-api/-/code-analyzer-engine-api-0.32.0.tgz#8e8c983c23c6621cc8f35e33a73549d507afdee5" - integrity sha512-A+OUm18ThU/AIg6GQfttfbDDtlHy9iZ7wy2+b9nf/U59P7/r9teiMvGJK/AIWQaFaTedcbzhazZ1w7mYX2Br7w== +"@salesforce/code-analyzer-engine-api@0.35.0", "@salesforce/code-analyzer-engine-api@^0.35.0": + version "0.35.0" + resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-engine-api/-/code-analyzer-engine-api-0.35.0.tgz#8b27fba651c4241ded7bcaa6a6ef3724258695d0" + integrity sha512-GIu4isesbNjuisC7kd19Nfy/FIFKHYjTC4BR1GgZWSMNylNpR8UvCMVEeydnYDTG6slxtXCIPdZOCIa0u9AkpA== dependencies: "@types/node" "^20.0.0" + minimatch "^10.2.1" -"@salesforce/code-analyzer-eslint-engine@^0.37.0": - version "0.37.0" - resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-eslint-engine/-/code-analyzer-eslint-engine-0.37.0.tgz#15cf1f9b9fdae61a31ffd9ca942348e4a2b6cd5a" - integrity sha512-RxpG4Vhg4gwCFt61D0Hen/8fz1+R03ThJ8tYYR2yOhg4RrYWFVK3hfVfIE6jvvgB1MW8cUn/tdd2eD/Z6w5Khg== +"@salesforce/code-analyzer-eslint-engine@^0.40.2": + version "0.40.2" + resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-eslint-engine/-/code-analyzer-eslint-engine-0.40.2.tgz#2fd169c53769543c38e2eae925bd8a930aab8961" + integrity sha512-dDjmSq9DooxYVl5uaxfl+iapqzfraYNdUIh7aDnbinPMIX4W1Ps/egKWH++pgYgB2GcxE2l4Q3oc1XH60EHf3g== dependencies: - "@eslint/css" "^0.14.1" - "@eslint/js" "^9.39.1" + "@babel/preset-react" "^7.28.5" + "@eslint/js" "^9.39.2" "@lwc/eslint-plugin-lwc" "^3.3.0" "@lwc/eslint-plugin-lwc-platform" "^6.3.0" - "@salesforce-ux/eslint-plugin-slds" "^1.0.6" - "@salesforce/code-analyzer-engine-api" "0.32.0" - "@salesforce/code-analyzer-eslint8-engine" "0.9.0" - "@salesforce/eslint-config-lwc" "^4.1.1" + "@salesforce-ux/eslint-plugin-slds" "^1.1.0" + "@salesforce/code-analyzer-engine-api" "0.35.0" + "@salesforce/code-analyzer-eslint8-engine" "0.12.0" + "@salesforce/eslint-config-lwc" "^4.1.2" "@salesforce/eslint-plugin-lightning" "^2.0.0" "@types/node" "^20.0.0" - "@typescript-eslint/eslint-plugin" "^8.47.0" - "@typescript-eslint/parser" "^8.47.0" - eslint "^9.39.1" + "@typescript-eslint/eslint-plugin" "^8.56.0" + "@typescript-eslint/parser" "^8.56.0" + eslint "^9.39.2" eslint-plugin-import "^2.32.0" - eslint-plugin-jest "^29.2.0" - globals "^16.5.0" - semver "^7.7.3" + eslint-plugin-jest "^29.15.0" + eslint-plugin-jsx-a11y "^6.10.2" + eslint-plugin-react "^7.37.5" + eslint-plugin-react-hooks "^7.0.1" + globals "^17.3.0" + semver "^7.7.4" typescript "^5.9.3" - typescript-eslint "^8.47.0" + typescript-eslint "^8.56.0" -"@salesforce/code-analyzer-eslint8-engine@0.9.0": - version "0.9.0" - resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-eslint8-engine/-/code-analyzer-eslint8-engine-0.9.0.tgz#f77ce7c600b029bed1809321009c1620f32b8c80" - integrity sha512-tM5/eSwX23Xt+RfKE41SCg68itBaCeHr3sF5nsSdlGhDuwzqD02GPomfdO+e2UwHJTq5aDssO1S5Hx8aoqQ+GA== +"@salesforce/code-analyzer-eslint8-engine@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-eslint8-engine/-/code-analyzer-eslint8-engine-0.12.0.tgz#ac7c778b576c63f5619281978ed6802db36aeb7a" + integrity sha512-zxIp7zuPy3v/OM9C+86O8dLhPoOzy6Rc/VWJKYfnPqxPI2Vand96WqRzcUlcqL8PvLFE18p8hthuIoXk2tF4Dw== dependencies: "@babel/core" "7.27.4" "@babel/eslint-parser" "7.27.5" "@eslint/js" "8.57.1" "@lwc/eslint-plugin-lwc" "2.2.0" "@lwc/eslint-plugin-lwc-platform" "5.2.0" - "@salesforce/code-analyzer-engine-api" "0.32.0" + "@salesforce/code-analyzer-engine-api" "0.35.0" "@salesforce/eslint-config-lwc" "3.7.2" "@salesforce/eslint-plugin-lightning" "1.0.1" "@types/node" "^20.0.0" @@ -2942,36 +3161,36 @@ typescript "5.8.3" typescript-eslint "8.30.1" -"@salesforce/code-analyzer-pmd-engine@^0.33.0": - version "0.33.0" - resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-pmd-engine/-/code-analyzer-pmd-engine-0.33.0.tgz#1ef4c62a7be4de94dba73b96537791b49477fc46" - integrity sha512-ofinhi/ixWE/g5HIjsGHURWKxe9g7seKhdNi31qmohH5POs9UI6LBD4g0GanEEXQdMNnLugUNuQaDVZSgMXLkA== +"@salesforce/code-analyzer-pmd-engine@^0.37.0": + version "0.37.0" + resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-pmd-engine/-/code-analyzer-pmd-engine-0.37.0.tgz#8189f07cdd468fa523a4d76bc1daa708111597cf" + integrity sha512-nvbRDiowBGZJg+q2j4okoz1cU+eFazXxi61RBGH62hsbwklW9WUYano3almZM8gFekNZWHbHEOCBx2syW1sNKA== dependencies: - "@salesforce/code-analyzer-engine-api" "0.32.0" + "@salesforce/code-analyzer-engine-api" "0.35.0" "@types/node" "^20.0.0" "@types/semver" "^7.7.1" - semver "^7.7.2" + semver "^7.7.4" -"@salesforce/code-analyzer-regex-engine@^0.30.0": - version "0.30.0" - resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-regex-engine/-/code-analyzer-regex-engine-0.30.0.tgz#ef85ca78e666d52d267bf6b4da854c3687260ce3" - integrity sha512-4RGw1qcZ0JTKKDflUyIXIKi20WexTCiJfg+C5W/SQaBPVAQ6BwqcGoJeeHGNqNFKmvns0XSckBFv+cepYfIcFA== +"@salesforce/code-analyzer-regex-engine@^0.33.0": + version "0.33.0" + resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-regex-engine/-/code-analyzer-regex-engine-0.33.0.tgz#fd5d4c85b311013b0c63ac67967a0b561a42b248" + integrity sha512-CgHKr43KZKGqU67jC/7inWvMXfsPoJTibCL3CyRTKSN66RImHS2WkF4LGn20+uYlE+yDsrxvWPDls28J46xDow== dependencies: - "@salesforce/code-analyzer-engine-api" "0.32.0" + "@salesforce/code-analyzer-engine-api" "0.35.0" "@types/node" "^20.0.0" - isbinaryfile "^5.0.7" + isbinaryfile "^5.0.0" p-limit "^3.1.0" -"@salesforce/code-analyzer-retirejs-engine@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-retirejs-engine/-/code-analyzer-retirejs-engine-0.29.0.tgz#25ef7c8e622d1d836e2555528ed26f78043b5c1b" - integrity sha512-5BAX4EA2rPkvlD3YMapvBXtEyaAtBk6cs2qK3s4Ug49OKHSSGtbAq94NNOfiVi1cR39mHc5qfA5V7GeN51m35w== +"@salesforce/code-analyzer-retirejs-engine@^0.32.0": + version "0.32.0" + resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-retirejs-engine/-/code-analyzer-retirejs-engine-0.32.0.tgz#4f64ca434a80ed970138a1d603c61ea6004b5c96" + integrity sha512-0jJe07ldPTWv3z1covThDIZHAo/fGwpLjWUZQq+11XLzK7ZB/52XUEXBIGKFw4hnKbPQguhCVmtKsiNi6ICfnA== dependencies: - "@salesforce/code-analyzer-engine-api" "0.32.0" + "@salesforce/code-analyzer-engine-api" "0.35.0" "@types/node" "^20.0.0" - isbinaryfile "^5.0.7" + isbinaryfile "^5.0.0" node-stream-zip "^1.15.0" - retire "^5.3.0" + retire "^5.4.2" "@salesforce/core@^5.3.20": version "5.3.20" @@ -3087,10 +3306,10 @@ eslint-restricted-globals "~0.2.0" semver "^7.6.2" -"@salesforce/eslint-config-lwc@^4.1.1": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@salesforce/eslint-config-lwc/-/eslint-config-lwc-4.1.1.tgz#9022d931ea20c683dc78ab5e95b17d0b8412c196" - integrity sha512-twgNRNnFgD0rwIgtROQEJ0m9QQhEAVuQ4DgO8fmSPZiKlsJsLduULeLZtVzok6qSWLY3ZImetgMBPF7mv+I1kw== +"@salesforce/eslint-config-lwc@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@salesforce/eslint-config-lwc/-/eslint-config-lwc-4.1.2.tgz#795606301842d15daf3553dc817eaaa172ae285d" + integrity sha512-GMoXOiqdSLYYuup8i6HWJYIScTGyOdgJCXXNzWb76Xa0RehqViigVAroTYB5OKvp4F5T7OLnGq06dARduphauQ== dependencies: "@babel/core" "~7.26.0" "@babel/eslint-parser" "~7.25.9" @@ -4162,7 +4381,7 @@ natural-compare "^1.4.0" ts-api-utils "^2.0.1" -"@typescript-eslint/eslint-plugin@8.48.0", "@typescript-eslint/eslint-plugin@^8.47.0": +"@typescript-eslint/eslint-plugin@8.48.0": version "8.48.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz#cdc9bdbe947713f658eb6109eeeea5d746824cf4" integrity sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ== @@ -4177,6 +4396,20 @@ natural-compare "^1.4.0" ts-api-utils "^2.1.0" +"@typescript-eslint/eslint-plugin@8.57.0", "@typescript-eslint/eslint-plugin@^8.56.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz#6e4085604ab63f55b3dcc61ce2c16965b2c36374" + integrity sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ== + dependencies: + "@eslint-community/regexpp" "^4.12.2" + "@typescript-eslint/scope-manager" "8.57.0" + "@typescript-eslint/type-utils" "8.57.0" + "@typescript-eslint/utils" "8.57.0" + "@typescript-eslint/visitor-keys" "8.57.0" + ignore "^7.0.5" + natural-compare "^1.4.0" + ts-api-utils "^2.4.0" + "@typescript-eslint/eslint-plugin@^6.21.0": version "6.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3" @@ -4205,7 +4438,7 @@ "@typescript-eslint/visitor-keys" "8.30.1" debug "^4.3.4" -"@typescript-eslint/parser@8.48.0", "@typescript-eslint/parser@^8.47.0": +"@typescript-eslint/parser@8.48.0": version "8.48.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.48.0.tgz#fc39ea9b1c8b2414c1f4b625277629e12a940e6b" integrity sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ== @@ -4216,6 +4449,17 @@ "@typescript-eslint/visitor-keys" "8.48.0" debug "^4.3.4" +"@typescript-eslint/parser@8.57.0", "@typescript-eslint/parser@^8.56.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.57.0.tgz#444c57a943e8b04f255cda18a94c8e023b46b08c" + integrity sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g== + dependencies: + "@typescript-eslint/scope-manager" "8.57.0" + "@typescript-eslint/types" "8.57.0" + "@typescript-eslint/typescript-estree" "8.57.0" + "@typescript-eslint/visitor-keys" "8.57.0" + debug "^4.4.3" + "@typescript-eslint/parser@^6.21.0": version "6.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" @@ -4236,6 +4480,15 @@ "@typescript-eslint/types" "^8.48.0" debug "^4.3.4" +"@typescript-eslint/project-service@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.57.0.tgz#2014ed527bcd0eff8aecb7e44879ae3150604ab3" + integrity sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w== + dependencies: + "@typescript-eslint/tsconfig-utils" "^8.57.0" + "@typescript-eslint/types" "^8.57.0" + debug "^4.4.3" + "@typescript-eslint/scope-manager@6.21.0": version "6.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1" @@ -4260,11 +4513,24 @@ "@typescript-eslint/types" "8.48.0" "@typescript-eslint/visitor-keys" "8.48.0" +"@typescript-eslint/scope-manager@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz#7d2a2aeaaef2ae70891b21939fadb4cb0b19f840" + integrity sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw== + dependencies: + "@typescript-eslint/types" "8.57.0" + "@typescript-eslint/visitor-keys" "8.57.0" + "@typescript-eslint/tsconfig-utils@8.48.0", "@typescript-eslint/tsconfig-utils@^8.48.0": version "8.48.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz#05cf091cd9f24a8e047783ff979136df6cf1be04" integrity sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w== +"@typescript-eslint/tsconfig-utils@8.57.0", "@typescript-eslint/tsconfig-utils@^8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz#cf2f2822af3887d25dd325b6bea6c3f60a83a0b4" + integrity sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA== + "@typescript-eslint/type-utils@6.21.0": version "6.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e" @@ -4296,6 +4562,17 @@ debug "^4.3.4" ts-api-utils "^2.1.0" +"@typescript-eslint/type-utils@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz#2877af4c2e8f0998b93a07dad1c34ce1bb669448" + integrity sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ== + dependencies: + "@typescript-eslint/types" "8.57.0" + "@typescript-eslint/typescript-estree" "8.57.0" + "@typescript-eslint/utils" "8.57.0" + debug "^4.4.3" + ts-api-utils "^2.4.0" + "@typescript-eslint/types@6.21.0": version "6.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" @@ -4311,6 +4588,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.48.0.tgz#f0dc5cf27217346e9b0d90556911e01d90d0f2a5" integrity sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA== +"@typescript-eslint/types@8.57.0", "@typescript-eslint/types@^8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.57.0.tgz#4fa5385ffd1cd161fa5b9dce93e0493d491b8dc6" + integrity sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg== + "@typescript-eslint/typescript-estree@6.21.0": version "6.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46" @@ -4354,6 +4636,21 @@ tinyglobby "^0.2.15" ts-api-utils "^2.1.0" +"@typescript-eslint/typescript-estree@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz#e0e4a89bfebb207de314826df876e2dabc7dea04" + integrity sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q== + dependencies: + "@typescript-eslint/project-service" "8.57.0" + "@typescript-eslint/tsconfig-utils" "8.57.0" + "@typescript-eslint/types" "8.57.0" + "@typescript-eslint/visitor-keys" "8.57.0" + debug "^4.4.3" + minimatch "^10.2.2" + semver "^7.7.3" + tinyglobby "^0.2.15" + ts-api-utils "^2.4.0" + "@typescript-eslint/utils@6.21.0": version "6.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" @@ -4387,6 +4684,16 @@ "@typescript-eslint/types" "8.48.0" "@typescript-eslint/typescript-estree" "8.48.0" +"@typescript-eslint/utils@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.57.0.tgz#c7193385b44529b788210d20c94c11de79ad3498" + integrity sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ== + dependencies: + "@eslint-community/eslint-utils" "^4.9.1" + "@typescript-eslint/scope-manager" "8.57.0" + "@typescript-eslint/types" "8.57.0" + "@typescript-eslint/typescript-estree" "8.57.0" + "@typescript-eslint/visitor-keys@6.21.0": version "6.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47" @@ -4411,6 +4718,14 @@ "@typescript-eslint/types" "8.48.0" eslint-visitor-keys "^4.2.1" +"@typescript-eslint/visitor-keys@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz#23aea662279bb66209700854453807a119350f85" + integrity sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg== + dependencies: + "@typescript-eslint/types" "8.57.0" + eslint-visitor-keys "^5.0.0" + "@typespec/ts-http-runtime@^0.3.0": version "0.3.1" resolved "https://registry.yarnpkg.com/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.1.tgz#2fa94050f25b4d85d0bc8b9d97874b8d347a9173" @@ -4658,6 +4973,16 @@ ajv@^6.12.4, ajv@^6.12.6: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^6.14.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a" + integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ajv@^8.0.1, ajv@^8.11.0, ajv@^8.12.0, ajv@^8.17.1: version "8.17.1" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" @@ -4789,6 +5114,11 @@ aria-hidden@^1.2.4: dependencies: tslib "^2.0.0" +aria-query@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" + integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== + array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" @@ -4807,7 +5137,7 @@ array-ify@^1.0.0: resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== -array-includes@^3.1.8, array-includes@^3.1.9: +array-includes@^3.1.6, array-includes@^3.1.8, array-includes@^3.1.9: version "3.1.9" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.9.tgz#1f0ccaa08e90cdbc3eb433210f903ad0f17c3f3a" integrity sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ== @@ -4826,6 +5156,18 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array.prototype.findlast@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" + integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + array.prototype.findlastindex@^1.2.5, array.prototype.findlastindex@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz#cfa1065c81dcb64e34557c9b81d012f6a421c564" @@ -4839,7 +5181,7 @@ array.prototype.findlastindex@^1.2.5, array.prototype.findlastindex@^1.2.6: es-object-atoms "^1.1.1" es-shim-unscopables "^1.1.0" -array.prototype.flat@^1.3.2, array.prototype.flat@^1.3.3: +array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2, array.prototype.flat@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz#534aaf9e6e8dd79fb6b9a9917f839ef1ec63afe5" integrity sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg== @@ -4859,6 +5201,17 @@ array.prototype.flatmap@^1.3.2, array.prototype.flatmap@^1.3.3: es-abstract "^1.23.5" es-shim-unscopables "^1.0.2" +array.prototype.tosorted@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" + integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + es-shim-unscopables "^1.0.2" + arraybuffer.prototype.slice@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" @@ -4887,6 +5240,11 @@ assertion-error@^2.0.1: resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== +ast-types-flow@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" + integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== + ast-types@^0.13.4: version "0.13.4" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" @@ -4904,10 +5262,10 @@ astring@~1.9.0: resolved "https://registry.yarnpkg.com/astring/-/astring-1.9.0.tgz#cc73e6062a7eb03e7d19c22d8b0b3451fd9bfeef" integrity sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg== -astronomical@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/astronomical/-/astronomical-2.0.1.tgz#43e583bc74fa23ec9f31cece0b5db3d01634fc5a" - integrity sha512-fBpcshxuu2x79LgFFl4qYWjw86NMkUgQeSA8mkw/XG6b5ccTD1EvdGhuRs9AZ0Njk3AcEbSNgi6K2qR4SOAwDQ== +astronomical@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/astronomical/-/astronomical-3.0.3.tgz#f7e97b0977998d055b15ecc597d71e77967dd3e9" + integrity sha512-nIIO2ADXfIHILJyD6l+UQ5qSGlMUjXvKdxFFrhyoRiN5ij+yNHPER+IYQ3n5BLDry+0hWBEJ62H2lY4B9yAz6A== dependencies: meriyah "^6.0.3" @@ -4965,6 +5323,16 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" +axe-core@^4.10.0: + version "4.11.1" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.11.1.tgz#052ff9b2cbf543f5595028b583e4763b40c78ea7" + integrity sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A== + +axobject-query@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" + integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -4980,6 +5348,11 @@ balanced-match@^3.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-3.0.1.tgz#e854b098724b15076384266497392a271f4a26a0" integrity sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w== +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -5065,6 +5438,13 @@ brace-expansion@^4.0.0: dependencies: balanced-match "^3.0.0" +brace-expansion@^5.0.2: + version "5.0.4" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.4.tgz#614daaecd0a688f660bbbc909a8748c3d80d4336" + integrity sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg== + dependencies: + balanced-match "^4.0.2" + braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -5767,6 +6147,11 @@ csv-stringify@^6.6.0: resolved "https://registry.yarnpkg.com/csv-stringify/-/csv-stringify-6.6.0.tgz#d384859cfb71d0a4a73c5bcc36a4daf5440cb033" integrity sha512-YW32lKOmIBgbxtu3g5SaiqWNwa/9ISQt2EcgOq0+RAIFufFp9is6tqNnKahqE5kuKvrnYAzs28r+s6pXJR8Vcw== +damerau-levenshtein@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" + integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== + dargs@^8.0.0: version "8.1.0" resolved "https://registry.yarnpkg.com/dargs/-/dargs-8.1.0.tgz#a34859ea509cbce45485e5aa356fef70bfcc7272" @@ -5908,7 +6293,7 @@ define-lazy-prop@^3.0.0: resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== -define-properties@^1.2.1: +define-properties@^1.1.3, define-properties@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== @@ -6108,6 +6493,66 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +es-abstract@^1.17.5, es-abstract@^1.23.3, es-abstract@^1.23.6, es-abstract@^1.24.1: + version "1.24.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.1.tgz#f0c131ed5ea1bb2411134a8dd94def09c46c7899" + integrity sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw== + dependencies: + array-buffer-byte-length "^1.0.2" + arraybuffer.prototype.slice "^1.0.4" + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + data-view-buffer "^1.0.2" + data-view-byte-length "^1.0.2" + data-view-byte-offset "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-set-tostringtag "^2.1.0" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.8" + get-intrinsic "^1.3.0" + get-proto "^1.0.1" + get-symbol-description "^1.1.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + internal-slot "^1.1.0" + is-array-buffer "^3.0.5" + is-callable "^1.2.7" + is-data-view "^1.0.2" + is-negative-zero "^2.0.3" + is-regex "^1.2.1" + is-set "^2.0.3" + is-shared-array-buffer "^1.0.4" + is-string "^1.1.1" + is-typed-array "^1.1.15" + is-weakref "^1.1.1" + math-intrinsics "^1.1.0" + object-inspect "^1.13.4" + object-keys "^1.1.1" + object.assign "^4.1.7" + own-keys "^1.0.1" + regexp.prototype.flags "^1.5.4" + safe-array-concat "^1.1.3" + safe-push-apply "^1.0.0" + safe-regex-test "^1.1.0" + set-proto "^1.0.0" + stop-iteration-iterator "^1.1.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.3" + typed-array-byte-length "^1.0.3" + typed-array-byte-offset "^1.0.4" + typed-array-length "^1.0.7" + unbox-primitive "^1.1.0" + which-typed-array "^1.1.19" + es-abstract@^1.23.2, es-abstract@^1.23.5, es-abstract@^1.23.9, es-abstract@^1.24.0: version "1.24.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.0.tgz#c44732d2beb0acc1ed60df840869e3106e7af328" @@ -6183,6 +6628,29 @@ es-html-parser@^1.0.0-alpha.4: resolved "https://registry.yarnpkg.com/es-html-parser/-/es-html-parser-1.0.0-alpha.8.tgz#4a93a56635e9c0ae7df091440cc7b5a1c299657d" integrity sha512-7zQHIugusEuMWjWafkdSwzDWGZ5EbRjSBp5mzfa8kwZoCS1zeiKLNV2SM7vbtPCo8xztWrukg5xZ7OGkYEoEbQ== +es-iterator-helpers@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.3.0.tgz#36ff394076e6ab50725bcd2b8cd64c4f5b4ea75b" + integrity sha512-04cg8iJFDOxWcYlu0GFFWgs7vtaEPCmr5w1nrj9V3z3axu/48HCMwK6VMp45Zh3ZB+xLP1ifbJfrq86+1ypKKQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-abstract "^1.24.1" + es-errors "^1.3.0" + es-set-tostringtag "^2.1.0" + function-bind "^1.1.2" + get-intrinsic "^1.3.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + internal-slot "^1.1.0" + iterator.prototype "^1.1.5" + math-intrinsics "^1.1.0" + safe-array-concat "^1.1.3" + es-module-lexer@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" @@ -6397,10 +6865,10 @@ eslint-plugin-jest@28.10.0: dependencies: "@typescript-eslint/utils" "^6.0.0 || ^7.0.0 || ^8.0.0" -eslint-plugin-jest@^29.2.0: - version "29.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-29.2.1.tgz#e56c5f79b6475dafa551ce8e762ac25d4bd21ea4" - integrity sha512-0WLIezrIxitUGbjMIGwznVzSIp0uFJV0PZ2fiSvpyVcxe+QMXKUt7MRhUpzdbctnnLwiOTOFkACplgB0wAglFw== +eslint-plugin-jest@^29.15.0: + version "29.15.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-29.15.0.tgz#58a5917a88244f7536ae10c68b5bd58d407896f0" + integrity sha512-ZCGr7vTH2WSo2hrK5oM2RULFmMruQ7W3cX7YfwoTiPfzTGTFBMmrVIz45jZHd++cGKj/kWf02li/RhTGcANJSA== dependencies: "@typescript-eslint/utils" "^8.0.0" @@ -6419,6 +6887,62 @@ eslint-plugin-jsdoc@^46.10.1: semver "^7.5.4" spdx-expression-parse "^4.0.0" +eslint-plugin-jsx-a11y@^6.10.2: + version "6.10.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz#d2812bb23bf1ab4665f1718ea442e8372e638483" + integrity sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q== + dependencies: + aria-query "^5.3.2" + array-includes "^3.1.8" + array.prototype.flatmap "^1.3.2" + ast-types-flow "^0.0.8" + axe-core "^4.10.0" + axobject-query "^4.1.0" + damerau-levenshtein "^1.0.8" + emoji-regex "^9.2.2" + hasown "^2.0.2" + jsx-ast-utils "^3.3.5" + language-tags "^1.0.9" + minimatch "^3.1.2" + object.fromentries "^2.0.8" + safe-regex-test "^1.0.3" + string.prototype.includes "^2.0.1" + +eslint-plugin-react-hooks@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz#66e258db58ece50723ef20cc159f8aa908219169" + integrity sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA== + dependencies: + "@babel/core" "^7.24.4" + "@babel/parser" "^7.24.4" + hermes-parser "^0.25.1" + zod "^3.25.0 || ^4.0.0" + zod-validation-error "^3.5.0 || ^4.0.0" + +eslint-plugin-react@^7.37.5: + version "7.37.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz#2975511472bdda1b272b34d779335c9b0e877065" + integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA== + dependencies: + array-includes "^3.1.8" + array.prototype.findlast "^1.2.5" + array.prototype.flatmap "^1.3.3" + array.prototype.tosorted "^1.1.4" + doctrine "^2.1.0" + es-iterator-helpers "^1.2.1" + estraverse "^5.3.0" + hasown "^2.0.2" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.9" + object.fromentries "^2.0.8" + object.values "^1.2.1" + prop-types "^15.8.1" + resolve "^2.0.0-next.5" + semver "^6.3.1" + string.prototype.matchall "^4.0.12" + string.prototype.repeat "^1.0.0" + eslint-plugin-unicorn@^50.0.1: version "50.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-50.0.1.tgz#e539cdb02dfd893c603536264c4ed9505b70e3bf" @@ -6485,6 +7009,11 @@ eslint-visitor-keys@^4.2.0, eslint-visitor-keys@^4.2.1: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== +eslint-visitor-keys@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be" + integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== + eslint@8.57.1, eslint@^8.56.0, eslint@^8.57.1: version "8.57.1" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" @@ -6569,6 +7098,46 @@ eslint@^9.32.0, eslint@^9.35.0, eslint@^9.39.1: natural-compare "^1.4.0" optionator "^0.9.3" +eslint@^9.39.2: + version "9.39.4" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.4.tgz#855da1b2e2ad66dc5991195f35e262bcec8117b5" + integrity sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ== + dependencies: + "@eslint-community/eslint-utils" "^4.8.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.21.2" + "@eslint/config-helpers" "^0.4.2" + "@eslint/core" "^0.17.0" + "@eslint/eslintrc" "^3.3.5" + "@eslint/js" "9.39.4" + "@eslint/plugin-kit" "^0.4.1" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.2" + "@types/estree" "^1.0.6" + ajv "^6.14.0" + chalk "^4.0.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.4.0" + eslint-visitor-keys "^4.2.1" + espree "^10.4.0" + esquery "^1.5.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + lodash.merge "^4.6.2" + minimatch "^3.1.5" + natural-compare "^1.4.0" + optionator "^0.9.3" + espree@^10.0.1, espree@^10.4.0: version "10.4.0" resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" @@ -6611,7 +7180,7 @@ estraverse@^4.1.1: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.1.0, estraverse@^5.2.0: +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== @@ -6802,10 +7371,17 @@ fast-uri@^3.0.1: resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== -fast-xml-builder@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fast-xml-builder/-/fast-xml-builder-1.1.3.tgz#283579acba94aecf998a7e1339bc7e037195abc1" - integrity sha512-1o60KoFw2+LWKQu3IdcfcFlGTW4dpqEWmjhYec6H82AYZU2TVBXep6tMl8Z1Y+wM+ZrzCwe3BZ9Vyd9N2rIvmg== +fast-xml-builder@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fast-xml-builder/-/fast-xml-builder-1.1.2.tgz#52a1f7d639ed2dcc5c144d6c58377296e52e6add" + integrity sha512-NJAmiuVaJEjVa7TjLZKlYd7RqmzOC91EtPFXHvlTcqBVo50Qh7XV5IwvXi1c7NRz2Q/majGX9YLcwJtWgHjtkA== + dependencies: + path-expression-matcher "^1.1.3" + +fast-xml-builder@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz#0c407a1d9d5996336c0cd76f7ff785cac6413017" + integrity sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg== dependencies: path-expression-matcher "^1.1.3" @@ -6824,11 +7400,20 @@ fast-xml-parser@^4.5.1, fast-xml-parser@^4.5.3: strnum "^1.1.1" fast-xml-parser@^5.3.6, fast-xml-parser@^5.4.1: - version "5.5.5" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.5.5.tgz#cadbcb992d6ac3f7e643d459506a8e1dd8adf5f2" - integrity sha512-NLY+V5NNbdmiEszx9n14mZBseJTC50bRq1VHsaxOmR72JDuZt+5J1Co+dC/4JPnyq+WrIHNM69r0sqf7BMb3Mg== + version "5.5.6" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.5.6.tgz#6fc61f5ae06a55a1f058abd6a4f4b5d3e9972cd0" + integrity sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw== + dependencies: + fast-xml-builder "^1.1.4" + path-expression-matcher "^1.1.3" + strnum "^2.1.2" + +fast-xml-parser@^5.5.3: + version "5.5.3" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.5.3.tgz#2166aef8ae1d3d9d0f1659f42882b5c5b93be7fb" + integrity sha512-Ymnuefk6VzAhT3SxLzVUw+nMio/wB1NGypHkgetwtXcK1JfryaHk4DWQFGVwQ9XgzyS5iRZ7C2ZGI4AMsdMZ6A== dependencies: - fast-xml-builder "^1.1.3" + fast-xml-builder "^1.1.2" path-expression-matcher "^1.1.3" strnum "^2.1.2" @@ -7158,7 +7743,7 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-proto@^1.0.1: +get-proto@^1.0.0, get-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== @@ -7331,10 +7916,10 @@ globals@^14.0.0: resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== -globals@^16.5.0: - version "16.5.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-16.5.0.tgz#ccf1594a437b97653b2be13ed4d8f5c9f850cac1" - integrity sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ== +globals@^17.3.0: + version "17.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-17.4.0.tgz#33d7d297ed1536b388a0e2f4bcd0ff19c8ff91b5" + integrity sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw== globals@~15.14.0: version "15.14.0" @@ -7489,6 +8074,18 @@ help-me@^5.0.0: resolved "https://registry.yarnpkg.com/help-me/-/help-me-5.0.0.tgz#b1ebe63b967b74060027c2ac61f9be12d354a6f6" integrity sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg== +hermes-estree@0.25.1: + version "0.25.1" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480" + integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw== + +hermes-parser@^0.25.1: + version "0.25.1" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1" + integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA== + dependencies: + hermes-estree "0.25.1" + hookified@^1.12.0, hookified@^1.12.1: version "1.12.1" resolved "https://registry.yarnpkg.com/hookified/-/hookified-1.12.1.tgz#b0de0116ca346fd6c4e55db901f52d5cd728ef00" @@ -8049,7 +8646,7 @@ isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== -isbinaryfile@^5.0.7: +isbinaryfile@^5.0.0: version "5.0.7" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-5.0.7.tgz#19a73f2281b7368dca9d3b3ac8a0434074670979" integrity sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ== @@ -8146,6 +8743,18 @@ istanbul-reports@^3.0.2, istanbul-reports@^3.1.7, istanbul-reports@^3.2.0: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +iterator.prototype@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz#12c959a29de32de0aa3bbbb801f4d777066dae39" + integrity sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g== + dependencies: + define-data-property "^1.1.4" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.6" + get-proto "^1.0.0" + has-symbols "^1.1.0" + set-function-name "^2.0.2" + jackspeak@^3.1.2: version "3.4.3" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" @@ -8355,6 +8964,16 @@ jsonwebtoken@9.0.3: ms "^2.1.1" semver "^7.5.4" +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5: + version "3.3.5" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" + integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== + dependencies: + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + object.assign "^4.1.4" + object.values "^1.1.6" + jszip@3.10.1, jszip@^3.10.1: version "3.10.1" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" @@ -8433,6 +9052,18 @@ known-css-properties@^0.37.0: resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.37.0.tgz#10ebe49b9dbb6638860ff8a002fb65a053f4aec5" integrity sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ== +language-subtag-registry@^0.3.20: + version "0.3.23" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7" + integrity sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ== + +language-tags@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777" + integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== + dependencies: + language-subtag-registry "^0.3.20" + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -8587,7 +9218,7 @@ long@^5.0.0: resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== -loose-envify@^1.1.0: +loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -8883,6 +9514,20 @@ minimatch@^10.0.3, minimatch@^10.1.1: dependencies: "@isaacs/brace-expansion" "^5.0.0" +minimatch@^10.2.1, minimatch@^10.2.2: + version "10.2.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde" + integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== + dependencies: + brace-expansion "^5.0.2" + +minimatch@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" + integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== + dependencies: + brace-expansion "^1.1.7" + minimatch@^5.0.1, minimatch@~5.1.1: version "5.1.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" @@ -9049,6 +9694,16 @@ nock@^13.5.6: json-stringify-safe "^5.0.1" propagate "^2.0.0" +node-exports-info@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/node-exports-info/-/node-exports-info-1.6.0.tgz#1aedafb01a966059c9a5e791a94a94d93f5c2a13" + integrity sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw== + dependencies: + array.prototype.flatmap "^1.3.3" + es-errors "^1.3.0" + object.entries "^1.1.9" + semver "^6.3.1" + node-fetch@^2.6.1: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" @@ -9164,7 +9819,7 @@ o11y_schema@^260.47.0: resolved "https://registry.yarnpkg.com/o11y_schema/-/o11y_schema-260.50.0.tgz#e886a2c80f44f19c94562714c173f8b2468dae06" integrity sha512-87ig+sAQ6ohHH0DmdX7I+sVcYpRXqRy676+VjBsrwkzwzMOCgKNmEGkxaTyqNZW5pJogdcvS8jy7zdcxuKyapQ== -object-assign@^4: +object-assign@^4, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -9179,7 +9834,7 @@ object-keys@^1.1.1: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.7: +object.assign@^4.1.4, object.assign@^4.1.7: version "4.1.7" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== @@ -9191,6 +9846,16 @@ object.assign@^4.1.7: has-symbols "^1.1.0" object-keys "^1.1.1" +object.entries@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.9.tgz#e4770a6a1444afb61bd39f984018b5bede25f8b3" + integrity sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-object-atoms "^1.1.1" + object.fromentries@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" @@ -9210,7 +9875,7 @@ object.groupby@^1.0.3: define-properties "^1.2.1" es-abstract "^1.23.2" -object.values@^1.2.0, object.values@^1.2.1: +object.values@^1.1.6, object.values@^1.2.0, object.values@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216" integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== @@ -9825,6 +10490,15 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== +prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + propagate@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" @@ -9976,6 +10650,11 @@ react-dom@^18.3.1: loose-envify "^1.1.0" scheduler "^0.23.2" +react-is@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + react-remove-scroll-bar@^2.3.7: version "2.3.8" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz#99c20f908ee467b385b68a3469b4a3e750012223" @@ -10115,7 +10794,7 @@ regexp-tree@^0.1.27: resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== -regexp.prototype.flags@^1.5.4: +regexp.prototype.flags@^1.5.3, regexp.prototype.flags@^1.5.4: version "1.5.4" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== @@ -10201,6 +10880,18 @@ resolve@^1.1.6, resolve@^1.10.0, resolve@^1.22.4, resolve@^1.22.8: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^2.0.0-next.5: + version "2.0.0-next.6" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.6.tgz#b3961812be69ace7b3bc35d5bf259434681294af" + integrity sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA== + dependencies: + es-errors "^1.3.0" + is-core-module "^2.16.1" + node-exports-info "^1.6.0" + object-keys "^1.1.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + responselike@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" @@ -10223,13 +10914,13 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" -retire@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/retire/-/retire-5.3.0.tgz#8ccfcaaea5fb34529a544f9e89d201acc5be6137" - integrity sha512-NHstwLMZQCCDW3iGcF1fuAwOlaYDPCcUqu1RgSw47aypPSDpIYWsDitK36HvQdMVNK3rUi14mYYUagCe7uumaQ== +retire@^5.4.2: + version "5.4.2" + resolved "https://registry.yarnpkg.com/retire/-/retire-5.4.2.tgz#31a9f6f57f216578222d8f09ee8c13f276c6de94" + integrity sha512-Qva7qmmMqEMrMma6saFpTgw083omqWXQet1Pd8xApSSQbrpFlySL1/nDaWTSh4MpbbTFzmV0ZKuQAlJiV5cNrQ== dependencies: ansi-colors "^4.1.1" - astronomical "^2.0.1" + astronomical "^3.0.0" commander "^10.0.1" proxy-agent "^6.4.0" uuid "^9.0.1" @@ -10368,7 +11059,7 @@ safe-push-apply@^1.0.0: es-errors "^1.3.0" isarray "^2.0.5" -safe-regex-test@^1.1.0: +safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== @@ -10419,6 +11110,11 @@ semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.2, semve resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== +semver@^7.7.4: + version "7.7.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + send@0.19.0: version "0.19.0" resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" @@ -10904,6 +11600,42 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" +string.prototype.includes@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz#eceef21283640761a81dbe16d6c7171a4edf7d92" + integrity sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + +string.prototype.matchall@^4.0.12: + version "4.0.12" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz#6c88740e49ad4956b1332a911e949583a275d4c0" + integrity sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-abstract "^1.23.6" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.6" + gopd "^1.2.0" + has-symbols "^1.1.0" + internal-slot "^1.1.0" + regexp.prototype.flags "^1.5.3" + set-function-name "^2.0.2" + side-channel "^1.1.0" + +string.prototype.repeat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz#e90872ee0308b29435aa26275f6e1b762daee01a" + integrity sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.trim@^1.2.10: version "1.2.10" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" @@ -11295,6 +12027,11 @@ ts-api-utils@^2.0.1, ts-api-utils@^2.1.0: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== +ts-api-utils@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8" + integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA== + ts-node@^10.9.2: version "10.9.2" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" @@ -11476,7 +12213,7 @@ typescript-eslint@8.30.1: "@typescript-eslint/parser" "8.30.1" "@typescript-eslint/utils" "8.30.1" -typescript-eslint@^8.37.0, typescript-eslint@^8.44.0, typescript-eslint@^8.47.0, typescript-eslint@^8.48.0: +typescript-eslint@^8.37.0, typescript-eslint@^8.44.0, typescript-eslint@^8.48.0: version "8.48.0" resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.48.0.tgz#1f0cfb33351f5740d5a289bf389b4ccacb64be42" integrity sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw== @@ -11486,6 +12223,16 @@ typescript-eslint@^8.37.0, typescript-eslint@^8.44.0, typescript-eslint@^8.47.0, "@typescript-eslint/typescript-estree" "8.48.0" "@typescript-eslint/utils" "8.48.0" +typescript-eslint@^8.56.0: + version "8.57.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.57.0.tgz#82764795d316ed1c72a489727c43c3a87373f100" + integrity sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA== + dependencies: + "@typescript-eslint/eslint-plugin" "8.57.0" + "@typescript-eslint/parser" "8.57.0" + "@typescript-eslint/typescript-estree" "8.57.0" + "@typescript-eslint/utils" "8.57.0" + typescript@5.8.3: version "5.8.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" @@ -12053,11 +12800,21 @@ zod-to-json-schema@^3.24.1, zod-to-json-schema@^3.24.3, zod-to-json-schema@^3.24 resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz#5920f020c4d2647edfbb954fa036082b92c9e12d" integrity sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg== +"zod-validation-error@^3.5.0 || ^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.2.tgz#bc605eba49ce0fcd598c127fee1c236be3f22918" + integrity sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ== + zod@^3.22.4, zod@^3.23.8, zod@^3.24.2, zod@^3.25.76: version "3.25.76" resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== +"zod@^3.25.0 || ^4.0.0": + version "4.3.6" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.3.6.tgz#89c56e0aa7d2b05107d894412227087885ab112a" + integrity sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg== + zod@^4.1.12: version "4.3.5" resolved "https://registry.yarnpkg.com/zod/-/zod-4.3.5.tgz#aeb269a6f9fc259b1212c348c7c5432aaa474d2a"