diff --git a/ci/diagnose.js b/ci/diagnose.js index 35919026b24..c7f90557686 100644 --- a/ci/diagnose.js +++ b/ci/diagnose.js @@ -506,8 +506,10 @@ function checkSupportedFrameworks (results, frameworks) { ) } - for (const note of framework.notes || []) { - addResult(results, 'info', `${framework.name} capability note`, note) + if (framework.notes) { + for (const note of framework.notes) { + addResult(results, 'info', `${framework.name} capability note`, note) + } } } } diff --git a/ci/test-optimization-validation/approval-artifacts.js b/ci/test-optimization-validation/approval-artifacts.js index 0bcd843d32d..ac087d018de 100644 --- a/ci/test-optimization-validation/approval-artifacts.js +++ b/ci/test-optimization-validation/approval-artifacts.js @@ -154,10 +154,16 @@ function getCoveredFilesManifest (material) { } for (const executable of material.executables) { if (executable.path && executable.sha256) files.set(executable.path, executable.sha256) - for (const delegated of executable.delegated || []) files.set(delegated.path, delegated.sha256) - for (const entrypoint of executable.entrypoints || []) files.set(entrypoint.path, entrypoint.sha256) + if (executable.delegated) { + for (const delegated of executable.delegated) files.set(delegated.path, delegated.sha256) + } + if (executable.entrypoints) { + for (const entrypoint of executable.entrypoints) files.set(entrypoint.path, entrypoint.sha256) + } + } + if (material.projectFiles) { + for (const projectFile of material.projectFiles) files.set(projectFile.path, projectFile.sha256) } - for (const projectFile of material.projectFiles || []) files.set(projectFile.path, projectFile.sha256) return [...files] .sort(([left], [right]) => left.localeCompare(right)) diff --git a/ci/test-optimization-validation/approval.js b/ci/test-optimization-validation/approval.js index 3ce0234edfa..4ab375e432f 100644 --- a/ci/test-optimization-validation/approval.js +++ b/ci/test-optimization-validation/approval.js @@ -240,21 +240,25 @@ function getApprovalCommand (id, command) { */ function getGeneratedFileMaterial (manifest, requestedScenario) { const files = [] - for (const framework of manifest.frameworks || []) { - const strategy = framework.generatedTestStrategy - const selectedPaths = getSelectedGeneratedPaths(strategy, requestedScenario) - for (const file of strategy?.files || []) { - if (!selectedPaths.has(path.resolve(file.path))) continue - const content = `${file.contentLines.join('\n')}\n` - files.push(sanitizeForReport({ - frameworkId: framework.id, - path: path.resolve(file.path), - sha256: crypto.createHash('sha256').update(content).digest('hex'), - content, - removeAfterValidation: (strategy.cleanupPaths || []).some(cleanupPath => { - return path.resolve(cleanupPath) === path.resolve(file.path) - }), - })) + if (manifest.frameworks) { + for (const framework of manifest.frameworks) { + const strategy = framework.generatedTestStrategy + const selectedPaths = getSelectedGeneratedPaths(strategy, requestedScenario) + if (strategy?.files) { + for (const file of strategy.files) { + if (!selectedPaths.has(path.resolve(file.path))) continue + const content = `${file.contentLines.join('\n')}\n` + files.push(sanitizeForReport({ + frameworkId: framework.id, + path: path.resolve(file.path), + sha256: crypto.createHash('sha256').update(content).digest('hex'), + content, + removeAfterValidation: (strategy.cleanupPaths || []).some(cleanupPath => { + return path.resolve(cleanupPath) === path.resolve(file.path) + }), + })) + } + } } } return files @@ -278,9 +282,11 @@ function getSelectedGeneratedPaths (strategy, requestedScenario) { return path.resolve(scenario.testIdentities[0].file) })) const selectedPaths = new Set() - for (const file of strategy.files || []) { - const filename = path.resolve(file.path) - if (!requestedScenario || !scenarioPaths.has(filename)) selectedPaths.add(filename) + if (strategy.files) { + for (const file of strategy.files) { + const filename = path.resolve(file.path) + if (!requestedScenario || !scenarioPaths.has(filename)) selectedPaths.add(filename) + } } if (generatedId) { const scenario = strategy.scenarios?.find(candidate => candidate.id === generatedId) diff --git a/ci/test-optimization-validation/ci-discovery.js b/ci/test-optimization-validation/ci-discovery.js index 483fc46bad8..28b86fe68fe 100644 --- a/ci/test-optimization-validation/ci-discovery.js +++ b/ci/test-optimization-validation/ci-discovery.js @@ -61,18 +61,22 @@ function buildCiDiscovery ({ manifest, diagnosis }) { function getManifestWorkflowLocations (manifest) { const root = manifest.repository?.root const locations = [] - for (const framework of manifest.frameworks || []) { - const configFile = framework.ciWiring?.configFile - if (typeof configFile !== 'string') continue - if (!root || !path.isAbsolute(configFile)) { - locations.push(configFile) - continue + if (manifest.frameworks) { + for (const framework of manifest.frameworks) { + const configFile = framework.ciWiring?.configFile + if (typeof configFile !== 'string') continue + if (!root || !path.isAbsolute(configFile)) { + locations.push(configFile) + continue + } + + const relative = path.relative(root, configFile) + const isRelativePath = relative && relative !== '..' && !relative.startsWith(`..${path.sep}`) && + !path.isAbsolute(relative) + locations.push(isRelativePath + ? relative.split(path.sep).join('/') + : configFile) } - - const relative = path.relative(root, configFile) - locations.push(relative && relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative) - ? relative.split(path.sep).join('/') - : configFile) } return uniqueStrings(locations) } @@ -106,12 +110,14 @@ function getCiDiscoveryContradictions ({ manifest, declaredFound, staticFound }) ) } - for (const framework of manifest.frameworks || []) { - if (!frameworkClaimsNoCi(framework)) continue - contradictions.push( - `framework ${framework.id || ''} records no CI workflow, but static diagnosis found ` + - formatList(staticFound) - ) + if (manifest.frameworks) { + for (const framework of manifest.frameworks) { + if (!frameworkClaimsNoCi(framework)) continue + contradictions.push( + `framework ${framework.id || ''} records no CI workflow, but static diagnosis found ` + + formatList(staticFound) + ) + } } return contradictions diff --git a/ci/test-optimization-validation/command-runner.js b/ci/test-optimization-validation/command-runner.js index c20b790af7d..ecf4e1b8c90 100644 --- a/ci/test-optimization-validation/command-runner.js +++ b/ci/test-optimization-validation/command-runner.js @@ -555,11 +555,13 @@ function buildOfflineValidationEnv ({ fixture, outputRoot }) { */ function assertNoInlineValidationEnvOverrides (command, env) { if (!env[VALIDATION_MODE_ENV]) return - for (const name of Object.keys(command.env || {})) { - const normalized = process.platform === 'win32' ? name.toUpperCase() : name - if (VALIDATION_RESERVED_ENV_NAMES.some(reserved => environmentNamesEqual(reserved, name)) || - isDatadogEnvironmentName(name) || normalized.startsWith('OTEL_')) { - throw new Error(`Direct-runner adapter must not override validator-controlled environment variable ${name}.`) + if (command.env) { + for (const name of Object.keys(command.env)) { + const normalized = process.platform === 'win32' ? name.toUpperCase() : name + if (VALIDATION_RESERVED_ENV_NAMES.some(reserved => environmentNamesEqual(reserved, name)) || + isDatadogEnvironmentName(name) || normalized.startsWith('OTEL_')) { + throw new Error(`Direct-runner adapter must not override validator-controlled environment variable ${name}.`) + } } } } diff --git a/ci/test-optimization-validation/environment.js b/ci/test-optimization-validation/environment.js index 7dbced529b3..bbbd8e5bb26 100644 --- a/ci/test-optimization-validation/environment.js +++ b/ci/test-optimization-validation/environment.js @@ -63,8 +63,10 @@ function setEnvironmentValue (environment, name, value, platform = process.platf * @returns {void} */ function mergeEnvironment (target, source, platform = process.platform) { - for (const [name, value] of Object.entries(source || {})) { - setEnvironmentValue(target, name, value, platform) + if (source) { + for (const [name, value] of Object.entries(source)) { + setEnvironmentValue(target, name, value, platform) + } } } diff --git a/ci/test-optimization-validation/executable.js b/ci/test-optimization-validation/executable.js index 0af544e623d..05201fe35ed 100644 --- a/ci/test-optimization-validation/executable.js +++ b/ci/test-optimization-validation/executable.js @@ -40,19 +40,21 @@ function getResolvedExecutable (command) { */ function bindManifestExecutables (manifest) { const identities = [] - for (const framework of manifest.frameworks || []) { - if (framework.status !== 'runnable') continue - const command = getManifestCommands({ frameworks: [framework] })[0]?.[1] - try { - const identity = getCommandExecutableIdentity(command, manifest.repository.root) - bindApprovedExecutable(framework.validation, identity) - identities.push({ id: `framework:${framework.id}`, ...identity }) - } catch (error) { - identities.push({ - id: `framework:${framework.id}`, - unavailable: true, - reason: error?.message || String(error), - }) + if (manifest.frameworks) { + for (const framework of manifest.frameworks) { + if (framework.status !== 'runnable') continue + const command = getManifestCommands({ frameworks: [framework] })[0]?.[1] + try { + const identity = getCommandExecutableIdentity(command, manifest.repository.root) + bindApprovedExecutable(framework.validation, identity) + identities.push({ id: `framework:${framework.id}`, ...identity }) + } catch (error) { + identities.push({ + id: `framework:${framework.id}`, + unavailable: true, + reason: error?.message || String(error), + }) + } } } return identities diff --git a/ci/test-optimization-validation/generated-files.js b/ci/test-optimization-validation/generated-files.js index 32ad84281ba..fccd9588ca7 100644 --- a/ci/test-optimization-validation/generated-files.js +++ b/ci/test-optimization-validation/generated-files.js @@ -91,14 +91,16 @@ function cleanupGeneratedFiles (manifest, { keep = false } = {}) { filesRemoved: 0, filesRetained: 0, } - for (const framework of manifest.frameworks || []) { - const strategy = framework.generatedTestStrategy - addCleanupOutcome( - outcome, - cleanupPaths(getSafeCleanupPaths(framework, strategy, { includeGeneratedFiles: true })), - 'files' - ) - addCleanupOutcome(outcome, cleanupCreatedDirectories(framework.project.root), 'directories') + if (manifest.frameworks) { + for (const framework of manifest.frameworks) { + const strategy = framework.generatedTestStrategy + addCleanupOutcome( + outcome, + cleanupPaths(getSafeCleanupPaths(framework, strategy, { includeGeneratedFiles: true })), + 'files' + ) + addCleanupOutcome(outcome, cleanupCreatedDirectories(framework.project.root), 'directories') + } } outcome.status = outcome.filesRetained > 0 || outcome.directoriesRetained > 0 ? 'incomplete' @@ -168,13 +170,15 @@ function initializeRuntimeCleanupFiles (framework, strategy) { if (initializedCleanupStrategies.has(strategy)) return const generatedFiles = new Set((strategy.files || []).map(file => validateGeneratedFilePath(framework, file.path))) - for (const cleanupPath of strategy.cleanupPaths || []) { - const filename = validateCleanupPath(framework, cleanupPath) - if (generatedFiles.has(filename) || isDirectory(filename) || !isNamespacedRuntimeFile(filename)) continue - if (fs.existsSync(filename)) { - throw new Error(`Refusing to delete pre-existing generated validation runtime file: ${filename}`) + if (strategy.cleanupPaths) { + for (const cleanupPath of strategy.cleanupPaths) { + const filename = validateCleanupPath(framework, cleanupPath) + if (generatedFiles.has(filename) || isDirectory(filename) || !isNamespacedRuntimeFile(filename)) continue + if (fs.existsSync(filename)) { + throw new Error(`Refusing to delete pre-existing generated validation runtime file: ${filename}`) + } + authorizedRuntimeCleanupFiles.set(filename, authorizePathForCleanup(framework.project.root, filename)) } - authorizedRuntimeCleanupFiles.set(filename, authorizePathForCleanup(framework.project.root, filename)) } initializedCleanupStrategies.add(strategy) } @@ -183,20 +187,24 @@ function getSafeCleanupPaths (framework, strategy, { includeGeneratedFiles }) { if (!strategy) return [] const generatedFiles = new Set() - for (const file of strategy.files || []) { - generatedFiles.add(validateGeneratedFilePath(framework, file.path)) + if (strategy.files) { + for (const file of strategy.files) { + generatedFiles.add(validateGeneratedFilePath(framework, file.path)) + } } const cleanupPaths = [] - for (const cleanupPath of strategy.cleanupPaths || []) { - const filename = validateCleanupPath(framework, cleanupPath) - if (generatedFiles.has(filename)) { - if (includeGeneratedFiles && writtenGeneratedFiles.has(filename)) cleanupPaths.push(filename) - continue - } + if (strategy.cleanupPaths) { + for (const cleanupPath of strategy.cleanupPaths) { + const filename = validateCleanupPath(framework, cleanupPath) + if (generatedFiles.has(filename)) { + if (includeGeneratedFiles && writtenGeneratedFiles.has(filename)) cleanupPaths.push(filename) + continue + } - if (authorizedRuntimeCleanupFiles.has(filename)) { - cleanupPaths.push(filename) + if (authorizedRuntimeCleanupFiles.has(filename)) { + cleanupPaths.push(filename) + } } } @@ -264,10 +272,12 @@ function authorizePathForCleanup (root, filename) { } function pinRuntimeCleanupParents (strategy) { - for (const cleanupPath of strategy.cleanupPaths || []) { - const authorization = authorizedRuntimeCleanupFiles.get(path.resolve(cleanupPath)) - if (authorization && authorization.physicalParent === undefined) { - pinCleanupParent(authorization, cleanupPath) + if (strategy.cleanupPaths) { + for (const cleanupPath of strategy.cleanupPaths) { + const authorization = authorizedRuntimeCleanupFiles.get(path.resolve(cleanupPath)) + if (authorization && authorization.physicalParent === undefined) { + pinCleanupParent(authorization, cleanupPath) + } } } } diff --git a/ci/test-optimization-validation/generated-verifier.js b/ci/test-optimization-validation/generated-verifier.js index b20e961bacb..05b47d1d5f9 100644 --- a/ci/test-optimization-validation/generated-verifier.js +++ b/ci/test-optimization-validation/generated-verifier.js @@ -209,14 +209,16 @@ function getVerificationFailure (framework, evidence, artifacts, scenario, resul function getGeneratedRuntimeFileStatus (strategy) { const generatedFiles = new Set((strategy.files || []).map(file => path.resolve(file.path))) let expectsRuntimeFile = false - for (const cleanupPath of strategy.cleanupPaths || []) { - const filename = path.resolve(cleanupPath) - if (generatedFiles.has(filename)) continue - expectsRuntimeFile = true - try { - const stat = fs.lstatSync(filename) - if (!stat.isSymbolicLink() && stat.isFile()) return true - } catch {} + if (strategy.cleanupPaths) { + for (const cleanupPath of strategy.cleanupPaths) { + const filename = path.resolve(cleanupPath) + if (generatedFiles.has(filename)) continue + expectsRuntimeFile = true + try { + const stat = fs.lstatSync(filename) + if (!stat.isSymbolicLink() && stat.isFile()) return true + } catch {} + } } return expectsRuntimeFile ? false : undefined } diff --git a/ci/test-optimization-validation/manifest-scaffold.js b/ci/test-optimization-validation/manifest-scaffold.js index 40fe6f60932..aa0f7a41bc4 100644 --- a/ci/test-optimization-validation/manifest-scaffold.js +++ b/ci/test-optimization-validation/manifest-scaffold.js @@ -457,15 +457,17 @@ function buildFramework (repositoryRoot, detection, ciDiscovery) { function getImplicitConfigFiles (framework, projectRoot, repositoryRoot) { const files = [] - for (const basename of IMPLICIT_CONFIG_FILENAMES[framework] || []) { - const filename = path.join(projectRoot, basename) - try { - const stat = fs.lstatSync(filename) - const physical = fs.realpathSync(filename) - if (stat.isFile() && !stat.isSymbolicLink() && - fs.statSync(physical).isFile() && - isPathInside(fs.realpathSync(repositoryRoot), physical)) files.push(physical) - } catch {} + if (IMPLICIT_CONFIG_FILENAMES[framework]) { + for (const basename of IMPLICIT_CONFIG_FILENAMES[framework]) { + const filename = path.join(projectRoot, basename) + try { + const stat = fs.lstatSync(filename) + const physical = fs.realpathSync(filename) + if (stat.isFile() && !stat.isSymbolicLink() && + fs.statSync(physical).isFile() && + isPathInside(fs.realpathSync(repositoryRoot), physical)) files.push(physical) + } catch {} + } } return files } @@ -1135,9 +1137,11 @@ function getFrameworkPackageJson (repositoryRoot, detection) { if (preciseLocation) return findOwningPackageJson(repositoryRoot, preciseLocation) const owners = new Map() - for (const location of detection.locations || []) { - const owner = findOwningPackageJson(repositoryRoot, location) - if (owner) owners.set(owner.path, owner) + if (detection.locations) { + for (const location of detection.locations) { + const owner = findOwningPackageJson(repositoryRoot, location) + if (owner) owners.set(owner.path, owner) + } } if (owners.size === 1) return [...owners.values()][0] } diff --git a/ci/test-optimization-validation/manifest-schema.js b/ci/test-optimization-validation/manifest-schema.js index 0bba03efd1f..622c7fce084 100644 --- a/ci/test-optimization-validation/manifest-schema.js +++ b/ci/test-optimization-validation/manifest-schema.js @@ -239,21 +239,23 @@ function validateRunnableFramework (repositoryRoot, framework, prefix, generated if (runnerInputError) errors.push(`${prefix}.validation runner configuration ${runnerInputError}.`) } validateStringArray(validation.requiredEnvVars, `${prefix}.validation.requiredEnvVars`, errors) - for (const name of validation.requiredEnvVars || []) { - if (!ENV_NAME_PATTERN.test(name)) { - errors.push(`${prefix}.validation.requiredEnvVars contains an invalid environment name.`) - } - if (/^(?:DD_|DATADOG_|OTEL_|NODE_OPTIONS$|TS_NODE_PROJECT$)/i.test(name)) { - errors.push( - `${prefix}.validation.requiredEnvVars must not inherit Datadog, OpenTelemetry, NODE_OPTIONS, or ` + - 'TS_NODE_PROJECT.' - ) - } - if (SECRET_ENV_PATTERN.test(name)) { - errors.push(`${prefix}.validation.requiredEnvVars must not inherit secret-like environment variables.`) - } - if (EXECUTION_ENV_PATTERN.test(name)) { - errors.push(`${prefix}.validation.requiredEnvVars must not inherit executable-loading environment variables.`) + if (validation.requiredEnvVars) { + for (const name of validation.requiredEnvVars) { + if (!ENV_NAME_PATTERN.test(name)) { + errors.push(`${prefix}.validation.requiredEnvVars contains an invalid environment name.`) + } + if (/^(?:DD_|DATADOG_|OTEL_|NODE_OPTIONS$|TS_NODE_PROJECT$)/i.test(name)) { + errors.push( + `${prefix}.validation.requiredEnvVars must not inherit Datadog, OpenTelemetry, NODE_OPTIONS, or ` + + 'TS_NODE_PROJECT.' + ) + } + if (SECRET_ENV_PATTERN.test(name)) { + errors.push(`${prefix}.validation.requiredEnvVars must not inherit secret-like environment variables.`) + } + if (EXECUTION_ENV_PATTERN.test(name)) { + errors.push(`${prefix}.validation.requiredEnvVars must not inherit executable-loading environment variables.`) + } } } if (!Number.isInteger(validation.timeoutMs) || validation.timeoutMs < 1 || validation.timeoutMs > MAX_TIMEOUT_MS) { diff --git a/ci/test-optimization-validation/plan-writer.js b/ci/test-optimization-validation/plan-writer.js index 11f4d4b6191..d4d2c00d4f4 100644 --- a/ci/test-optimization-validation/plan-writer.js +++ b/ci/test-optimization-validation/plan-writer.js @@ -176,7 +176,9 @@ function formatApprovalPlan ({ ? [`Blocker: ${plain(framework.blockerCategory.replaceAll('_', ' '))}.`] : []) ) - for (const note of framework.notes || []) lines.push(`- ${plain(note)}`) + if (framework.notes) { + for (const note of framework.notes) lines.push(`- ${plain(note)}`) + } lines.push('') continue } diff --git a/ci/test-optimization-validation/runner-command.js b/ci/test-optimization-validation/runner-command.js index a6bd6841bac..27fc1b85efa 100644 --- a/ci/test-optimization-validation/runner-command.js +++ b/ci/test-optimization-validation/runner-command.js @@ -50,12 +50,18 @@ function getFrameworkCommands (framework, requestedScenario = null) { if (requestedScenario === 'ci-wiring') return [] const commands = [['basic-reporting', getBasicCommand(framework)]] - for (const [index, fallback] of (framework.validation.fallbackTests || []).entries()) { - commands.push([`basic-reporting:fallback-${index + 1}`, getBasicCommand(framework, fallback.testFile)]) + const fallbackTests = framework.validation.fallbackTests + if (fallbackTests) { + for (const [index, fallback] of fallbackTests.entries()) { + commands.push([`basic-reporting:fallback-${index + 1}`, getBasicCommand(framework, fallback.testFile)]) + } } - for (const scenario of framework.generatedTestStrategy?.scenarios || []) { - if (!shouldIncludeGeneratedScenario(scenario.id, requestedScenario)) continue - commands.push([`generated:${scenario.id}`, getGeneratedCommand(framework, scenario)]) + const scenarios = framework.generatedTestStrategy?.scenarios + if (scenarios) { + for (const scenario of scenarios) { + if (!shouldIncludeGeneratedScenario(scenario.id, requestedScenario)) continue + commands.push([`generated:${scenario.id}`, getGeneratedCommand(framework, scenario)]) + } } return commands } @@ -69,9 +75,11 @@ function getFrameworkCommands (framework, requestedScenario = null) { */ function getManifestCommands (manifest, requestedScenario = null) { const commands = [] - for (const framework of manifest.frameworks || []) { - for (const [label, command] of getFrameworkCommands(framework, requestedScenario)) { - commands.push([`${framework.id}:${label}`, command]) + if (manifest.frameworks) { + for (const framework of manifest.frameworks) { + for (const [label, command] of getFrameworkCommands(framework, requestedScenario)) { + commands.push([`${framework.id}:${label}`, command]) + } } } return commands @@ -87,15 +95,23 @@ function getManifestCommands (manifest, requestedScenario = null) { */ function getManifestInputFiles (manifest, { includeLocal = true } = {}) { const files = new Set() - for (const framework of manifest.frameworks || []) { - addExistingFile(files, framework.ciWiring?.configFile) - addExistingFile(files, framework.project?.packageJson) - if (!includeLocal) continue - if (framework.status !== 'runnable') continue - addExistingFile(files, framework.validation?.runner) - addExistingFile(files, framework.validation?.testFile) - for (const fallback of framework.validation?.fallbackTests || []) addExistingFile(files, fallback.testFile) - for (const filename of framework.project?.configFiles || []) addExistingFile(files, filename) + if (manifest.frameworks) { + for (const framework of manifest.frameworks) { + addExistingFile(files, framework.ciWiring?.configFile) + addExistingFile(files, framework.project?.packageJson) + if (!includeLocal) continue + if (framework.status !== 'runnable') continue + addExistingFile(files, framework.validation?.runner) + addExistingFile(files, framework.validation?.testFile) + const fallbackTests = framework.validation?.fallbackTests + if (fallbackTests) { + for (const fallback of fallbackTests) addExistingFile(files, fallback.testFile) + } + const configFiles = framework.project?.configFiles + if (configFiles) { + for (const filename of configFiles) addExistingFile(files, filename) + } + } } return [...files].sort() } diff --git a/ci/test-optimization-validation/runner-contract.js b/ci/test-optimization-validation/runner-contract.js index f2d5e018edc..895ed52d8d5 100644 --- a/ci/test-optimization-validation/runner-contract.js +++ b/ci/test-optimization-validation/runner-contract.js @@ -432,10 +432,12 @@ function getRunnerInputError (args, environment, projectRoot, repositoryRoot, co if (inputs.error) return inputs.error const approved = new Set() - for (const filename of configFiles || []) { - try { - approved.add(fs.realpathSync(filename)) - } catch {} + if (configFiles) { + for (const filename of configFiles) { + try { + approved.add(fs.realpathSync(filename)) + } catch {} + } } const unbound = inputs.files.find(filename => !approved.has(filename)) if (unbound) return `references an input that is not approval-bound: ${unbound}` diff --git a/ci/test-optimization-validation/static-diagnosis.js b/ci/test-optimization-validation/static-diagnosis.js index 636fc035940..c080d112655 100644 --- a/ci/test-optimization-validation/static-diagnosis.js +++ b/ci/test-optimization-validation/static-diagnosis.js @@ -121,8 +121,11 @@ function getExactFrameworkLocations (diagnosis, framework) { const locations = new Set() addRelativeFrameworkLocation(locations, diagnosis, framework.project?.packageJson) - for (const configFile of framework.project?.configFiles || []) { - addRelativeFrameworkLocation(locations, diagnosis, configFile) + const configFiles = framework.project?.configFiles + if (configFiles) { + for (const configFile of configFiles) { + addRelativeFrameworkLocation(locations, diagnosis, configFile) + } } return locations diff --git a/ci/vitest-no-worker-init-setup.mjs b/ci/vitest-no-worker-init-setup.mjs index 2372e1baff9..feb84f3e4c4 100644 --- a/ci/vitest-no-worker-init-setup.mjs +++ b/ci/vitest-no-worker-init-setup.mjs @@ -109,26 +109,29 @@ if (isNoWorkerInitActive) { } function applyExecutionChanges (suite) { - for (const task of suite?.tasks || []) { - if (task.type === 'suite') { - applyExecutionChanges(task) - continue - } + const tasks = suite?.tasks + if (tasks) { + for (const task of tasks) { + if (task.type === 'suite') { + applyExecutionChanges(task) + continue + } - const testSuite = getTestSuite(task) - const testName = getTestName(task) - if (attemptToFixTests[testSuite]?.[testName]) { - task.retry = 0 - task.repeats = attemptToFixRetries - task.meta.__ddTestOptAtfRetries = attemptToFixRetries - } else if (disabledTests[testSuite]?.[testName]) { - task.mode = 'skip' - } else if (isEarlyFlakeDetectionTest(testSuite, testName)) { - task.retry = 0 - task.repeats = earlyFlakeDetectionRetries - task.meta.__ddTestOptEfdRetries = earlyFlakeDetectionRetries + const testSuite = getTestSuite(task) + const testName = getTestName(task) + if (attemptToFixTests[testSuite]?.[testName]) { + task.retry = 0 + task.repeats = attemptToFixRetries + task.meta.__ddTestOptAtfRetries = attemptToFixRetries + } else if (disabledTests[testSuite]?.[testName]) { + task.mode = 'skip' + } else if (isEarlyFlakeDetectionTest(testSuite, testName)) { + task.retry = 0 + task.repeats = earlyFlakeDetectionRetries + task.meta.__ddTestOptEfdRetries = earlyFlakeDetectionRetries + } + wrapRetryCondition(task) } - wrapRetryCondition(task) } } diff --git a/eslint.config.mjs b/eslint.config.mjs index 2290258b348..40f8cf51b56 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -645,6 +645,7 @@ export default [ ...eslintPluginUnicorn.configs.recommended.rules, // Not in `recommended`: the innerHTML sink class and unread object properties. + 'unicorn/iteration-fallback-style': 'error', 'unicorn/no-unsafe-dom-html': 'error', 'unicorn/no-unused-properties': 'error', @@ -699,6 +700,7 @@ export default [ 'unicorn/prefer-simple-condition-first': 'off', // lots | needs a short-circuit behavior audit 'unicorn/prefer-then-catch': 'off', // many | broadens rejection boundaries 'unicorn/require-array-sort-compare': 'off', // many | many intentional lexicographic sorts + 'unicorn/single-line-block-comment-style': 'off', // lots | preserve compact JSDoc typedefs // The following rules should not be activated! 'unicorn/consistent-boolean-name': 'off', // Would rename public API and config booleans diff --git a/package.json b/package.json index 5d57dad60f8..0ead1995ffc 100644 --- a/package.json +++ b/package.json @@ -219,7 +219,7 @@ "eslint-plugin-n": "^18.2.1", "eslint-plugin-promise": "^7.3.0", "eslint-plugin-sonarjs": "^4.2.0", - "eslint-plugin-unicorn": "^72.0.0", + "eslint-plugin-unicorn": "^73.0.0", "express": "^5.1.0", "glob": "^10.4.5", "globals": "^17.7.0", diff --git a/packages/datadog-instrumentations/src/helpers/router-helper.js b/packages/datadog-instrumentations/src/helpers/router-helper.js index cc32812bc3f..05c2a231944 100644 --- a/packages/datadog-instrumentations/src/helpers/router-helper.js +++ b/packages/datadog-instrumentations/src/helpers/router-helper.js @@ -68,12 +68,14 @@ function collectRoutesFromRouter (router, prefix) { const fullPaths = getRouteFullPaths(route, prefix) for (const fullPath of fullPaths) { - for (const [method, enabled] of Object.entries(route.methods || {})) { - if (!enabled) continue - routeAddedChannel.publish({ - method: normalizeMethodName(method), - path: fullPath, - }) + if (route.methods) { + for (const [method, enabled] of Object.entries(route.methods)) { + if (!enabled) continue + routeAddedChannel.publish({ + method: normalizeMethodName(method), + path: fullPath, + }) + } } } } else if (layer.handle?.stack?.length) { diff --git a/packages/datadog-instrumentations/src/jest.js b/packages/datadog-instrumentations/src/jest.js index e8dd88f7f63..f1ece57984a 100644 --- a/packages/datadog-instrumentations/src/jest.js +++ b/packages/datadog-instrumentations/src/jest.js @@ -2254,16 +2254,15 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) { this.#efdRetryGatesByName = undefined this.#detachedEfdRetryQueue = undefined testSuiteDatadogEnvironments.delete(this.testSuiteAbsolutePath) - } - if (event.name === 'test_skip' || event.name === 'test_todo') { + } else if (event.name === 'test_skip' || event.name === 'test_todo') { const testName = getJestTestName(event.test) const retryGates = this.#efdRetryGatesByName?.get(testName) - if (retryGates && efdRetryMetadataByTest.has(event.test)) { - this.#discardedEfdRetryTests ??= new Set() - this.#discardedEfdRetryTests.add(event.test) - return - } if (retryGates) { + if (efdRetryMetadataByTest.has(event.test)) { + this.#discardedEfdRetryTests ??= new Set() + this.#discardedEfdRetryTests.add(event.test) + return + } for (const gate of retryGates) { gate.resolve(false) } diff --git a/packages/datadog-instrumentations/src/jest/coverage-backfill.js b/packages/datadog-instrumentations/src/jest/coverage-backfill.js index b1ed08bc63c..4bc9bea5c0e 100644 --- a/packages/datadog-instrumentations/src/jest/coverage-backfill.js +++ b/packages/datadog-instrumentations/src/jest/coverage-backfill.js @@ -14,7 +14,7 @@ const TRANSFORM_OPTIONS = { function getCoverageBackfillFiles (skippableSuitesCoverage, rootDir, getTestSuitePath) { const files = [] - for (const filename of Object.keys(skippableSuitesCoverage || {})) { + for (const filename of Object.keys(skippableSuitesCoverage)) { const relativeFilename = path.isAbsolute(filename) ? getTestSuitePath(filename, rootDir) : filename diff --git a/packages/datadog-instrumentations/src/mocha/worker.js b/packages/datadog-instrumentations/src/mocha/worker.js index 0d4f6d26cca..93d98ca050b 100644 --- a/packages/datadog-instrumentations/src/mocha/worker.js +++ b/packages/datadog-instrumentations/src/mocha/worker.js @@ -239,7 +239,7 @@ function getWebdriverioHookTest (hook) { */ function adjustWebdriverioHookFailures (runner) { let suppressedFailures = 0 - for (const { test } of runnerToFailedHooks.get(runner) || []) { + for (const { test } of runnerToFailedHooks.get(runner)) { if (isWebdriverioFailureSuppressed(test)) { suppressedFailures++ } @@ -281,7 +281,7 @@ function getWebdriverioSuiteResults (runner) { } }) - for (const { file, test } of runnerToFailedHooks.get(runner) || []) { + for (const { file, test } of runnerToFailedHooks.get(runner)) { const result = resultsByFile.get(file) if (result && !isWebdriverioFailureSuppressed(test)) { result.status = 'fail' diff --git a/packages/datadog-instrumentations/src/vitest-worker.js b/packages/datadog-instrumentations/src/vitest-worker.js index b483f1d989e..bbaa73157c4 100644 --- a/packages/datadog-instrumentations/src/vitest-worker.js +++ b/packages/datadog-instrumentations/src/vitest-worker.js @@ -137,8 +137,8 @@ function isFileInRepository (filename, repositoryRoot) { } function isV8ScriptCovered (scriptCoverage) { - for (const functionCoverage of scriptCoverage.functions || []) { - for (const range of functionCoverage.ranges || []) { + for (const functionCoverage of scriptCoverage.functions) { + for (const range of functionCoverage.ranges) { if (range.count > 0) return true } } @@ -147,15 +147,18 @@ function isV8ScriptCovered (scriptCoverage) { function getCoveredFilesFromV8Result (coverage, repositoryRoot) { const coveredFiles = [] - for (const scriptCoverage of coverage?.result || []) { - if (!isV8ScriptCovered(scriptCoverage)) continue + const scriptCoverageResults = coverage?.result + if (scriptCoverageResults) { + for (const scriptCoverage of scriptCoverageResults) { + if (!isV8ScriptCovered(scriptCoverage)) continue - const coverageFilename = getCoverageFilename(scriptCoverage.url) - if (!coverageFilename) continue + const coverageFilename = getCoverageFilename(scriptCoverage.url) + if (!coverageFilename) continue - const filename = realpath(coverageFilename) - if (isFileInRepository(filename, repositoryRoot)) { - coveredFiles.push(filename) + const filename = realpath(coverageFilename) + if (isFileInRepository(filename, repositoryRoot)) { + coveredFiles.push(filename) + } } } return coveredFiles diff --git a/packages/datadog-instrumentations/src/webdriverio.js b/packages/datadog-instrumentations/src/webdriverio.js index 4a6a81a0a00..2b9ed41231e 100644 --- a/packages/datadog-instrumentations/src/webdriverio.js +++ b/packages/datadog-instrumentations/src/webdriverio.js @@ -1002,9 +1002,13 @@ launcherStartInstanceCh.subscribe({ const state = getCoordinatorState(localRunner) addScheduledFiles(state, context.arguments?.[0] || []) - for (const schedule of context.self._schedule || []) { - for (const { files } of schedule.specs || []) { - addScheduledFiles(state, files) + if (context.self._schedule) { + for (const schedule of context.self._schedule) { + if (schedule.specs) { + for (const { files } of schedule.specs) { + addScheduledFiles(state, files) + } + } } } }, diff --git a/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js b/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js index 6ee7b84f96d..0cca3fef773 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +++ b/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js @@ -487,16 +487,18 @@ function extractMessagesFromConverseContent (role, contentBlocks) { const toolCalls = [] const toolResults = [] - for (const block of contentBlocks || []) { - if (block == null || typeof block !== 'object') continue - if (typeof block.text === 'string') { - content += block.text - } else if (block.toolUse) { - toolCalls.push(buildToolCall(block.toolUse)) - } else if (block.toolResult) { - toolResults.push(buildToolResult(block.toolResult)) - } else { - content += `[Unsupported content type: ${getContentBlockType(block)}]` + if (contentBlocks) { + for (const block of contentBlocks) { + if (block == null || typeof block !== 'object') continue + if (typeof block.text === 'string') { + content += block.text + } else if (block.toolUse) { + toolCalls.push(buildToolCall(block.toolUse)) + } else if (block.toolResult) { + toolResults.push(buildToolResult(block.toolResult)) + } else { + content += `[Unsupported content type: ${getContentBlockType(block)}]` + } } } @@ -572,14 +574,17 @@ function buildUsage (usage = {}) { */ function extractConverseToolDefinitions (params) { const toolDefinitions = [] - for (const tool of params.toolConfig?.tools || []) { - const toolSpec = tool?.toolSpec - if (!toolSpec?.name) continue - toolDefinitions.push({ - name: toolSpec.name, - description: toolSpec.description ?? '', - schema: toolSpec.inputSchema ?? {}, - }) + const tools = params.toolConfig?.tools + if (tools) { + for (const tool of tools) { + const toolSpec = tool?.toolSpec + if (!toolSpec?.name) continue + toolDefinitions.push({ + name: toolSpec.name, + description: toolSpec.description ?? '', + schema: toolSpec.inputSchema ?? {}, + }) + } } return toolDefinitions } @@ -593,13 +598,17 @@ function extractConverseToolDefinitions (params) { */ function extractRequestParamsConverse (params) { const prompt = [] - for (const block of params.system || []) { - if (typeof block?.text === 'string') prompt.push({ content: block.text, role: 'system' }) + if (params.system) { + for (const block of params.system) { + if (typeof block?.text === 'string') prompt.push({ content: block.text, role: 'system' }) + } } - for (const msg of params.messages || []) { - if (msg == null || typeof msg !== 'object') continue - const message = extractMessagesFromConverseContent(msg.role || 'user', msg.content) - if (message) prompt.push(message) + if (params.messages) { + for (const msg of params.messages) { + if (msg == null || typeof msg !== 'object') continue + const message = extractMessagesFromConverseContent(msg.role || 'user', msg.content) + if (message) prompt.push(message) + } } const { temperature, topP, maxTokens, stopSequences } = params.inferenceConfig || {} @@ -632,7 +641,7 @@ function extractTextAndResponseReasonConverse (response) { * response, spread across start/delta chunks. We reassemble those chunks * into a normalized content-block array and reuse the non-stream extractor. * - * @param {Array} chunks - Ordered ConverseStreamOutput events. + * @param {Array | undefined} chunks - Ordered ConverseStreamOutput events. * @returns {Generation} */ function extractTextAndResponseReasonConverseFromStream (chunks) { @@ -641,29 +650,31 @@ function extractTextAndResponseReasonConverseFromStream (chunks) { let usage = {} const blocksByIdx = new Map() - for (const chunk of chunks || []) { - if (chunk.messageStart?.role) { - role = chunk.messageStart.role - } else if (chunk.messageStop?.stopReason) { - stopReason = chunk.messageStop.stopReason - } else if (chunk.metadata?.usage) { - usage = chunk.metadata.usage - } else if (chunk.contentBlockStart?.start?.toolUse) { - const { contentBlockIndex, start: { toolUse } } = chunk.contentBlockStart - blocksByIdx.set(contentBlockIndex, { - toolUse: { toolUseId: toolUse.toolUseId, name: toolUse.name, inputStr: '' }, - }) - } else if (chunk.contentBlockDelta) { - const { contentBlockIndex, delta } = chunk.contentBlockDelta - if (typeof delta?.text === 'string') { - const block = blocksByIdx.get(contentBlockIndex) ?? {} - block.text = (block.text ?? '') + delta.text - blocksByIdx.set(contentBlockIndex, block) - } else if (typeof delta?.toolUse?.input === 'string') { - const block = blocksByIdx.get(contentBlockIndex) ?? { toolUse: { inputStr: '' } } - block.toolUse ??= { inputStr: '' } - block.toolUse.inputStr += delta.toolUse.input - blocksByIdx.set(contentBlockIndex, block) + if (chunks) { + for (const chunk of chunks) { + if (chunk.messageStart?.role) { + role = chunk.messageStart.role + } else if (chunk.messageStop?.stopReason) { + stopReason = chunk.messageStop.stopReason + } else if (chunk.metadata?.usage) { + usage = chunk.metadata.usage + } else if (chunk.contentBlockStart?.start?.toolUse) { + const { contentBlockIndex, start: { toolUse } } = chunk.contentBlockStart + blocksByIdx.set(contentBlockIndex, { + toolUse: { toolUseId: toolUse.toolUseId, name: toolUse.name, inputStr: '' }, + }) + } else if (chunk.contentBlockDelta) { + const { contentBlockIndex, delta } = chunk.contentBlockDelta + if (typeof delta?.text === 'string') { + const block = blocksByIdx.get(contentBlockIndex) ?? {} + block.text = (block.text ?? '') + delta.text + blocksByIdx.set(contentBlockIndex, block) + } else if (typeof delta?.toolUse?.input === 'string') { + const block = blocksByIdx.get(contentBlockIndex) ?? { toolUse: { inputStr: '' } } + block.toolUse ??= { inputStr: '' } + block.toolUse.inputStr += delta.toolUse.input + blocksByIdx.set(contentBlockIndex, block) + } } } } diff --git a/packages/datadog-plugin-aws-sdk/test/bedrockruntime.util.spec.js b/packages/datadog-plugin-aws-sdk/test/bedrockruntime.util.spec.js index df4cc6f905c..762481fef05 100644 --- a/packages/datadog-plugin-aws-sdk/test/bedrockruntime.util.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/bedrockruntime.util.spec.js @@ -9,6 +9,36 @@ const { } = require('../src/services/bedrockruntime/utils') describe('bedrockruntime converse stream extractor', () => { + it('returns an empty message when the stream fails before yielding a chunk', () => { + const generation = extractTextAndResponseReasonConverseFromStream() + + assert.deepStrictEqual(generation.messages, [{ role: 'assistant', content: '' }]) + }) + + it('aggregates streamed text, tool input, metadata, and the stop reason', () => { + const generation = extractTextAndResponseReasonConverseFromStream([ + { messageStart: { role: 'assistant' } }, + { contentBlockDelta: { contentBlockIndex: 0, delta: { text: 'hel' } } }, + { contentBlockDelta: { contentBlockIndex: 0, delta: { text: 'lo' } } }, + { contentBlockDelta: { contentBlockIndex: 1, delta: { toolUse: { input: '{"city":"Berlin"}' } } } }, + { metadata: { usage: { inputTokens: 2, outputTokens: 3 } } }, + { messageStop: { stopReason: 'tool_use' } }, + ]) + + assert.deepStrictEqual(generation.messages, [{ + role: 'assistant', + content: 'hello', + toolCalls: [{ name: '', arguments: { city: 'Berlin' }, toolId: '', type: 'toolUse' }], + }]) + assert.deepStrictEqual(generation.usage, { + inputTokens: 2, + outputTokens: 3, + cacheReadTokens: undefined, + cacheWriteTokens: undefined, + }) + assert.strictEqual(generation.finishReason, 'tool_use') + }) + it('emits empty tool-call arguments when the streamed tool-use input is malformed JSON', () => { const generation = extractTextAndResponseReasonConverseFromStream([ { messageStart: { role: 'assistant' } }, diff --git a/packages/datadog-plugin-electron/src/net.js b/packages/datadog-plugin-electron/src/net.js index 1f8675f1482..07bbaf96345 100644 --- a/packages/datadog-plugin-electron/src/net.js +++ b/packages/datadog-plugin-electron/src/net.js @@ -64,12 +64,18 @@ class ElectronRequestPlugin extends HttpClientPlugin { const responseHead = ctx.res?._responseHead const { statusCode } = responseHead || {} - for (const header in ctx.req._urlLoaderOptions?.headers || {}) { - reqHeaders[header.name] = header.value + const requestHeaders = ctx.req._urlLoaderOptions?.headers + if (requestHeaders) { + for (const header in requestHeaders) { + reqHeaders[header.name] = header.value + } } - for (const header in responseHead?.rawHeaders || {}) { - resHeaders[header.name] = header.value + const responseHeaders = responseHead?.rawHeaders + if (responseHeaders) { + for (const header in responseHeaders) { + resHeaders[header.name] = header.value + } } ctx.req = { headers: reqHeaders } diff --git a/packages/datadog-plugin-mocha/src/index.js b/packages/datadog-plugin-mocha/src/index.js index cac667f58bf..4849d048a03 100644 --- a/packages/datadog-plugin-mocha/src/index.js +++ b/packages/datadog-plugin-mocha/src/index.js @@ -767,7 +767,7 @@ class MochaPlugin extends CiPlugin { const state = this._webdriverioJasmineState const results = [] const reportedFiles = new Set() - for (const [file, status] of state?.suiteStatuses || []) { + for (const [file, status] of state.suiteStatuses) { const error = state.suiteErrors.get(file) const result = { file, status } if (error) { @@ -779,7 +779,7 @@ class MochaPlugin extends CiPlugin { results.push(result) reportedFiles.add(file) } - for (const spec of state?.specs || []) { + for (const spec of state.specs) { const file = normalizeJasmineFile(spec) if (!reportedFiles.has(file)) { results.push({ file, status: 'skip' }) diff --git a/packages/datadog-plugin-vitest/src/index.js b/packages/datadog-plugin-vitest/src/index.js index 6c4dce36773..b9a8c0df3fa 100644 --- a/packages/datadog-plugin-vitest/src/index.js +++ b/packages/datadog-plugin-vitest/src/index.js @@ -593,7 +593,7 @@ class VitestPlugin extends CiPlugin { isVitestNoWorkerInitActive, onDone, }) => { - for (const [tag, value] of Object.entries(requestErrorTags || {})) { + for (const [tag, value] of Object.entries(requestErrorTags)) { this.testSessionSpan.setTag(tag, value) this.testModuleSpan.setTag(tag, value) } diff --git a/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js b/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js index 1700b4550cb..4acd084f19e 100644 --- a/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +++ b/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js @@ -34,8 +34,11 @@ function parseSkippableSuitesResponse ( validateSkippableTestsResponse(parsedResponse, { validationMode }) } const coverage = {} - for (const [filename, bitmap] of Object.entries(parsedResponse.meta?.coverage || {})) { - coverage[filename.replaceAll('\\', '/')] = bitmap + const coverageByFilename = parsedResponse.meta?.coverage + if (coverageByFilename) { + for (const [filename, bitmap] of Object.entries(coverageByFilename)) { + coverage[filename.replaceAll('\\', '/')] = bitmap + } } const skippableItems = parsedResponse diff --git a/packages/dd-trace/src/config/index.js b/packages/dd-trace/src/config/index.js index 4ee295e072c..99864e47874 100644 --- a/packages/dd-trace/src/config/index.js +++ b/packages/dd-trace/src/config/index.js @@ -194,8 +194,11 @@ class Config extends ConfigBase { this.debug = log.configure(options) // Process stable config warnings, if any - for (const warning of this.stableConfig?.warnings ?? []) { - log.warn(warning) + const stableConfigWarnings = this.stableConfig?.warnings + if (stableConfigWarnings) { + for (const warning of stableConfigWarnings) { + log.warn(warning) + } } this.#applyDefaults() diff --git a/packages/dd-trace/src/config/remote_config.js b/packages/dd-trace/src/config/remote_config.js index a95ca899a79..644e05d54b0 100644 --- a/packages/dd-trace/src/config/remote_config.js +++ b/packages/dd-trace/src/config/remote_config.js @@ -236,7 +236,7 @@ const optionLookupTable = { const transformers = { tracing_sampling_rules (samplingRules) { - for (const rule of (samplingRules || [])) { + for (const rule of samplingRules) { if (rule.tags) { const reformattedTags = {} for (const tag of rule.tags) { diff --git a/packages/dd-trace/src/llmobs/experiments/index.js b/packages/dd-trace/src/llmobs/experiments/index.js index 9eb5ee282a4..2ae22d4181c 100644 --- a/packages/dd-trace/src/llmobs/experiments/index.js +++ b/packages/dd-trace/src/llmobs/experiments/index.js @@ -47,15 +47,17 @@ class Experiments { : (descriptionOrOptions ?? {}) const dataset = new Dataset(this.#client, name, options.description ?? '') const recordIds = new Set() - for (const record of options.records ?? []) { - if (record.id !== undefined && (typeof record.id !== 'string' || record.id.length === 0)) { - throw new Error('record id must be a non-empty string') - } - if (record.id !== undefined) { - if (recordIds.has(record.id)) throw new Error(`Duplicate record id '${record.id}'`) - recordIds.add(record.id) + if ((options.records) != null) { + for (const record of options.records) { + if (record.id !== undefined && (typeof record.id !== 'string' || record.id.length === 0)) { + throw new Error('record id must be a non-empty string') + } + if (record.id !== undefined) { + if (recordIds.has(record.id)) throw new Error(`Duplicate record id '${record.id}'`) + recordIds.add(record.id) + } + dataset.addRecord(new DatasetRecord(record.inputData, record.expectedOutput, record.metadata, record.id)) } - dataset.addRecord(new DatasetRecord(record.inputData, record.expectedOutput, record.metadata, record.id)) } return dataset } @@ -82,13 +84,16 @@ class Experiments { 'GET', `${API_BASE_PATH}/${projectId}/datasets?filter[name]=${encodeURIComponent(name)}` ) - for (const item of listed?.data ?? []) { - if (item?.attributes?.name === name) { - datasetId = String(item?.id ?? '') - description = String(item?.attributes?.description ?? '') - latestVersion = item?.attributes?.current_version ?? null - datasetVersion = version ?? latestVersion - break + const datasets = listed?.data + if (datasets) { + for (const item of datasets) { + if (item?.attributes?.name === name) { + datasetId = String(item?.id ?? '') + description = String(item?.attributes?.description ?? '') + latestVersion = item?.attributes?.current_version ?? null + datasetVersion = version ?? latestVersion + break + } } } if (datasetId === null) return false @@ -107,16 +112,19 @@ class Experiments { 'GET', `${API_BASE_PATH}/${projectId}/datasets/${datasetId}/records?${query.toString()}` ) - for (const item of resp?.data ?? []) { - const attrs = item?.attributes ?? item - const recordId = String(item?.id ?? attrs?.id ?? '') - recs.push(new DatasetRecord( - attrs?.input ?? null, - attrs?.expected_output ?? null, - attrs?.metadata ?? {}, - recordId === '' ? null : recordId - )) - ids.push(recordId) + const recordData = resp?.data + if (recordData) { + for (const item of recordData) { + const attrs = item?.attributes ?? item + const recordId = String(item?.id ?? attrs?.id ?? '') + recs.push(new DatasetRecord( + attrs?.input ?? null, + attrs?.expected_output ?? null, + attrs?.metadata ?? {}, + recordId === '' ? null : recordId + )) + ids.push(recordId) + } } cursor = resp?.meta?.after ?? '' if (!cursor) break diff --git a/packages/dd-trace/src/llmobs/experiments/util.js b/packages/dd-trace/src/llmobs/experiments/util.js index 9236f9098c7..002b0c98519 100644 --- a/packages/dd-trace/src/llmobs/experiments/util.js +++ b/packages/dd-trace/src/llmobs/experiments/util.js @@ -166,8 +166,10 @@ function stringify (value) { */ function buildTags (userTags, autoTags) { const tags = new Map() - for (const [key, value] of Object.entries(userTags ?? {})) { - tags.set(key, `${key}:${value}`) + if ((userTags) != null) { + for (const [key, value] of Object.entries(userTags)) { + tags.set(key, `${key}:${value}`) + } } for (const [key, value] of Object.entries(autoTags)) { if (value !== undefined && value !== null && value !== '') tags.set(key, `${key}:${value}`) diff --git a/packages/dd-trace/src/sampling_rule.js b/packages/dd-trace/src/sampling_rule.js index 04787057033..28b82c771d6 100644 --- a/packages/dd-trace/src/sampling_rule.js +++ b/packages/dd-trace/src/sampling_rule.js @@ -180,8 +180,10 @@ class SamplingRule { if (resource) { this.matchers.push(matcher(resource, resourceLocator)) } - for (const [key, value] of Object.entries(tags || {})) { - this.matchers.push(matcher(value, makeTagLocator(key))) + if (tags) { + for (const [key, value] of Object.entries(tags)) { + this.matchers.push(matcher(value, makeTagLocator(key))) + } } this._sampler = new Sampler(sampleRate) diff --git a/packages/dd-trace/src/telemetry/telemetry.js b/packages/dd-trace/src/telemetry/telemetry.js index af6f5d37b76..cce74abb312 100644 --- a/packages/dd-trace/src/telemetry/telemetry.js +++ b/packages/dd-trace/src/telemetry/telemetry.js @@ -130,7 +130,7 @@ function updateRetryData (error, retryObj) { function getIntegrations () { const newIntegrations = /** @type {Integration[]} */ ([]) - for (const pluginName of Object.keys(pluginManager._pluginsByName ?? {})) { + for (const pluginName of Object.keys(pluginManager._pluginsByName)) { if (!sentIntegrations.has(pluginName)) { newIntegrations.push({ name: pluginName, diff --git a/scripts/check_licenses.js b/scripts/check_licenses.js index c54f95b30ff..13fb872a120 100644 --- a/scripts/check_licenses.js +++ b/scripts/check_licenses.js @@ -203,16 +203,18 @@ function addNpmProductionDependencies (dependencies, packageLockPath) { function addDependencyPatterns (patterns, manifest, context, includePeers = false) { const optionalDependencies = manifest.optionalDependencies ?? {} - for (const [name, range] of Object.entries(manifest.dependencies ?? {})) { - if (!Object.hasOwn(optionalDependencies, name)) { - addDependencyPattern(patterns, name, range, context) + if ((manifest.dependencies) != null) { + for (const [name, range] of Object.entries(manifest.dependencies)) { + if (!Object.hasOwn(optionalDependencies, name)) { + addDependencyPattern(patterns, name, range, context) + } } } for (const [name, range] of Object.entries(optionalDependencies)) { addDependencyPattern(patterns, name, range, context) } - if (includePeers) { - for (const [name, range] of Object.entries(manifest.peerDependencies ?? {})) { + if (includePeers && (manifest.peerDependencies) != null) { + for (const [name, range] of Object.entries(manifest.peerDependencies)) { addDependencyPattern(patterns, name, range, context, manifest.optionalPeers?.includes(name)) } } diff --git a/scripts/generate-config-types.js b/scripts/generate-config-types.js index 235a07a1f62..418f5d49fdc 100644 --- a/scripts/generate-config-types.js +++ b/scripts/generate-config-types.js @@ -253,9 +253,11 @@ function generateEnvVarConfigTypes (supportedConfigurations) { const type = getEnvVarType(propertyName, entry) envVarTypes.set(canonicalName, type) - for (const alias of entry.aliases ?? []) { - if (!supportedConfigurations[alias] && !envVarTypes.has(alias)) { - envVarTypes.set(alias, type) + if ((entry.aliases) != null) { + for (const alias of entry.aliases) { + if (!supportedConfigurations[alias] && !envVarTypes.has(alias)) { + envVarTypes.set(alias, type) + } } } } diff --git a/scripts/generate-supported-integrations.js b/scripts/generate-supported-integrations.js index f8958cd156c..db877961872 100644 --- a/scripts/generate-supported-integrations.js +++ b/scripts/generate-supported-integrations.js @@ -108,9 +108,11 @@ function readInstrumentationRanges (engines) { */ function lowestVersion (ranges) { let lowest - for (const range of ranges ?? []) { - const candidate = semver.minVersion(range) - if (candidate && (!lowest || semver.lt(candidate, lowest))) lowest = candidate + if ((ranges) != null) { + for (const range of ranges) { + const candidate = semver.minVersion(range) + if (candidate && (!lowest || semver.lt(candidate, lowest))) lowest = candidate + } } return lowest?.version ?? '' } diff --git a/scripts/mocha-run-file.js b/scripts/mocha-run-file.js index 51c083ba694..741baa49f36 100644 --- a/scripts/mocha-run-file.js +++ b/scripts/mocha-run-file.js @@ -67,11 +67,13 @@ async function main () { reporterOptions: config.reporterOptions, }) - for (const req of config.require ?? []) { - // Resolve relative to repo root (cwd), matching Mocha CLI behavior. - const mod = require(path.resolve(req)) - if (mod?.mochaHooks) { - mocha.rootHooks(mod.mochaHooks) + if ((config.require) != null) { + for (const req of config.require) { + // Resolve relative to repo root (cwd), matching Mocha CLI behavior. + const mod = require(path.resolve(req)) + if (mod?.mochaHooks) { + mocha.rootHooks(mod.mochaHooks) + } } } diff --git a/yarn.lock b/yarn.lock index 6fdd6c5e87f..b116865b23d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2068,10 +2068,10 @@ eslint-plugin-sonarjs@^4.2.0: typescript ">=5 <6.1.0" yaml "^2.9.0" -eslint-plugin-unicorn@^72.0.0: - version "72.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-72.0.0.tgz#3d8caf428fd84c02457e6f152128fd1944ce19a9" - integrity sha512-hqO6ksoOHO+ZhdseTuKRVQbx9U7PRO/cv8qAR1mctwzdVO2hYud8uS9luAhp43RJgziYgHAph8eHyipT8GL0ng== +eslint-plugin-unicorn@^73.0.0: + version "73.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-73.0.0.tgz#665a56b95b4fdae0e1e72ee66dab38c6dd0260ba" + integrity sha512-V0YatLe9nkGhXEXKe2Qljb1EY0sJHwDV0HUF1NKFwtsHh/fU7qGHDgv+6fchzZcgU2/7noHo2gdjnmo0P2uDPw== dependencies: "@eslint-community/eslint-utils" "^4.9.1" "@eslint/css-tree" "^4.0.4"