Files
renovate/lib/modules/manager/gradle/parser/registry-urls.ts
Sebastian Poxhofer 58771bb183 refactor(linters): enabled oxlints func-style (#44691)
* refactor(oxlint): enable func-style

Enable the built-in func-style rule as ["warn", "declaration"] so that
named functions are written as function declarations instead of arrow or
function expressions assigned to variables, matching the convention in
docs/development/best-practices.md (Functions section). Inline arrow
callbacks are unaffected; prefer-arrow-callback continues to cover the
callback direction.

Severity is "warn" instead of "error" as a fallback: a repo-wide run
found 196 pre-existing violations (70 in spec files, 126 in production
code), exceeding the 80-violation fix threshold for this change, so no
violations were fixed here. Follow-up work can convert the offenders
(largest clusters: lib/modules/versioning/ruby, lib/util/cache/package,
lib/modules/versioning/maven, lib/modules/versioning/gradle) and then
raise the rule to "error". Note oxlint 1.72 also supports an
allowTypeAnnotation option if typed consts should remain arrows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(linter): enforce func-style at error

* refactor(func-style): convert const-arrow functions to declarations in with-cache spec and ruby versioning

* refactor(func-style): convert const-arrow functions to declarations in maven/gradle modules

* refactor(func-style): convert const-arrow functions to declarations in remaining versioning modules

* refactor(func-style): convert const-arrow functions to declarations in platform modules

Discriminated-union closures (ensureCommentRemoval byTopic/byContent
helpers in github, gitlab, bitbucket, bitbucket-server) needed the
narrowed field passed explicitly as a parameter, since function
declarations are hoisted and lose the outer if-narrowing that inline
arrow-function closures previously relied on.

* refactor(func-style): convert const-arrow functions to declarations across util/manager/datasource modules

One case (logger/renovate-logger.ts logOnceFn) is kept as an arrow
function with a documented oxlint-disable: it is stored on
this.logger.once[level] and relies on lexically-bound this, which a
hoisted function declaration would not preserve.

* refactor(func-style): convert remaining const-arrow test helpers to declarations

get-updated.spec.ts: hoisted expectPipCompilePackageAndLockFile to
module scope (it only closes over the module-level pipCompile mock)
and added a documented oxlint-disable for vitest/expect-expect on its
caller test, since oxlint's expect-expect check does not trace
assertions through an externally-defined helper function.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 11:14:22 +00:00

233 lines
5.9 KiB
TypeScript

import type { lexer, parser } from '@renovatebot/good-enough-parser';
import { query as q } from '@renovatebot/good-enough-parser';
import { regEx } from '../../../../util/regex.ts';
import type { Ctx } from '../types.ts';
import { qApplyFrom } from './apply-from.ts';
import { qAssignments } from './assignments.ts';
import {
REGISTRY_URLS,
cleanupTempVars,
qArtifactId,
qGroupId,
qValueMatcher,
qVersion,
storeInTokenMap,
storeVarToken,
} from './common.ts';
import { handleRegistryContent, handleRegistryUrl } from './handlers.ts';
import { qPlugins } from './plugins.ts';
function cleanupTmpContentSpec(ctx: Ctx): Ctx {
ctx.tmpRegistryContent = [];
return ctx;
}
function qContentDescriptorSpec(
methodName: RegExp,
matcher: q.QueryBuilder<Ctx, parser.Node>,
): q.QueryBuilder<Ctx, parser.Node> {
return q
.sym<Ctx>(methodName, storeVarToken)
.handler((ctx) => storeInTokenMap(ctx, 'methodName'))
.alt(
// includeGroup "foo.bar"
matcher,
// includeGroup("foo.bar")
q.tree({
type: 'wrapped-tree',
maxDepth: 1,
startsWith: '(',
endsWith: ')',
search: q.begin<Ctx>().join(matcher).end(),
}),
);
}
// includeModule('foo')
// excludeModuleByRegex('bar')
function qContentDescriptor(
mode: 'include' | 'exclude',
): q.QueryBuilder<Ctx, parser.Node> {
return q
.alt<Ctx>(
qContentDescriptorSpec(
regEx(
`^(?:${mode}Group|${mode}GroupByRegex|${mode}GroupAndSubgroups)$`,
),
qGroupId,
),
qContentDescriptorSpec(
regEx(`^(?:${mode}Module|${mode}ModuleByRegex)$`),
q.join(qGroupId, q.op(','), qArtifactId),
),
qContentDescriptorSpec(
regEx(`^(?:${mode}Version|${mode}VersionByRegex)$`),
q.join(qGroupId, q.op(','), qArtifactId, q.op(','), qVersion),
),
)
.handler(handleRegistryContent);
}
// content { includeModule('foo'); excludeModule('bar') }
const qRegistryContent = q.sym<Ctx>('content').tree({
type: 'wrapped-tree',
maxDepth: 1,
startsWith: '{',
endsWith: '}',
search: q.alt(qContentDescriptor('include'), qContentDescriptor('exclude')),
});
// uri("https://foo.bar/baz")
// "https://foo.bar/baz"
const qUri = q
.alt(
q.sym<Ctx>('uri').tree({
maxDepth: 1,
search: qValueMatcher,
}),
qValueMatcher,
)
.handler((ctx) => storeInTokenMap(ctx, 'registryUrl'));
// mavenCentral()
// mavenCentral { ... }
const qPredefinedRegistries = q
.sym(
regEx(`^(?:${Object.keys(REGISTRY_URLS).join('|')})$`),
(ctx: Ctx, node: lexer.Token) => {
const nodeTransformed: lexer.Token = {
...node,
type: 'string-value',
value: REGISTRY_URLS[node.value as keyof typeof REGISTRY_URLS],
};
storeVarToken(ctx, nodeTransformed);
return ctx;
},
)
.handler((ctx) => storeInTokenMap(ctx, 'registryUrl'))
.alt(
q
.tree({
type: 'wrapped-tree',
startsWith: '(',
endsWith: ')',
search: q.begin<Ctx>().end(),
})
.opt(q.op<Ctx>('.').join(qRegistryContent)),
q.tree({
type: 'wrapped-tree',
startsWith: '{',
endsWith: '}',
search: q.opt(qRegistryContent),
}),
);
// { url = "https://some.repo"; content { ... } }
const qMavenArtifactRegistry = q.tree({
type: 'wrapped-tree',
maxDepth: 1,
startsWith: '{',
endsWith: '}',
search: q.alt(
q
.sym<Ctx>('name')
.opt(q.op('='))
.join(qValueMatcher)
.handler((ctx) => storeInTokenMap(ctx, 'name')),
q.sym<Ctx>('url').opt(q.op('=')).join(qUri),
q.sym<Ctx>('setUrl').tree({
maxDepth: 1,
startsWith: '(',
endsWith: ')',
search: q.begin<Ctx>().join(qUri).end(),
}),
qRegistryContent,
),
});
// maven(url = uri("https://foo.bar/baz"))
// maven("https://foo.bar/baz") { content { ... } }
// maven { name = some; url = "https://foo.bar/${name}" }
const qCustomRegistryUrl = q.sym<Ctx>('maven').alt(
q
.tree<Ctx>({
type: 'wrapped-tree',
maxDepth: 1,
startsWith: '(',
endsWith: ')',
search: q.begin<Ctx>().opt(q.sym<Ctx>('url').op('=')).join(qUri).end(),
})
.opt(qMavenArtifactRegistry),
qMavenArtifactRegistry,
);
// forRepository { maven { ... } }
const qForRepository = q.sym<Ctx>('forRepository').tree({
type: 'wrapped-tree',
maxDepth: 1,
maxMatches: 1,
startsWith: '{',
endsWith: '}',
search: q.alt<Ctx>(qPredefinedRegistries, qCustomRegistryUrl),
});
// filter { includeGroup(...); includeModule(...) }
const qFilter = q.sym<Ctx>('filter').tree({
type: 'wrapped-tree',
maxDepth: 1,
startsWith: '{',
endsWith: '}',
search: qContentDescriptor('include'),
});
// exclusiveContent { forRepository { ... }; filter { ... } }
const qExclusiveContent = q
.sym<Ctx>('exclusiveContent', storeVarToken)
.handler((ctx) => storeInTokenMap(ctx, 'registryType'))
.tree({
type: 'wrapped-tree',
maxDepth: 1,
maxMatches: 1,
startsWith: '{',
endsWith: '}',
search: q.alt<Ctx>(
q.join<Ctx>(qForRepository, qFilter),
q.join<Ctx>(qFilter, qForRepository),
),
});
const qRegistries = q
.alt(qExclusiveContent, qPredefinedRegistries, qCustomRegistryUrl)
.handler(handleRegistryUrl)
.handler(cleanupTmpContentSpec)
.handler(cleanupTempVars);
const qPluginManagement = q.sym<Ctx>('pluginManagement', storeVarToken).tree({
type: 'wrapped-tree',
startsWith: '{',
endsWith: '}',
preHandler: (ctx) => {
ctx.tmpTokenStore.registryScope = ctx.varTokens;
ctx.varTokens = [];
return ctx;
},
search: q
.handler<Ctx>((ctx) => {
if (ctx.tmpTokenStore.registryScope) {
ctx.tokenMap.registryScope = ctx.tmpTokenStore.registryScope;
}
return ctx;
})
.alt(qAssignments, qApplyFrom, qPlugins, qRegistries),
postHandler: (ctx) => {
delete ctx.tmpTokenStore.registryScope;
return ctx;
},
});
export const qRegistryUrls = q.alt<Ctx>(
q.sym<Ctx>('publishing').tree(),
qPluginManagement,
qRegistries,
);