import type { lexer, parser } from '@renovatebot/good-enough-parser'; import { lang, query as q } from '@renovatebot/good-enough-parser'; import { RANGE_PATTERN } from '@renovatebot/pep440'; import { regEx } from '../../../util/regex.ts'; import { normalizePythonDepName } from '../../datasource/pypi/common.ts'; import { PypiDatasource } from '../../datasource/pypi/index.ts'; import type { ExtractConfig, PackageDependency, PackageFileContent, } from '../types.ts'; interface ManagerData { lineNumber: number; } type Context = PackageFileContent; const python = lang.createLang('python'); // Optimize regex memory usage when we don't need named groups function cleanupNamedGroups(regexSource: string): string { return regexSource.replace(regEx(/\(\?<\w+>/g), '(?:'); } const rangePattern = cleanupNamedGroups(RANGE_PATTERN); const versionPattern = `(?:${rangePattern}(?:\\s*,\\s*${rangePattern})*)`; const depNamePattern = '(?:[a-zA-Z][-_a-zA-Z0-9\\.]*[a-zA-Z0-9])'; const depPattern = [ '^', `(?${depNamePattern})`, `(?(?:\\[\\s*(?:${depNamePattern}(?:\\s*,\\s*${depNamePattern})*\\s*)\\])?)`, `(?${versionPattern})`, ].join('\\s*'); const extractRegex = regEx(depPattern); // Extract dependency string function depStringHandler( ctx: Context, token: lexer.StringValueToken, ): Context { const depStr = token.value; const match = extractRegex.exec(depStr); // TODO #22198 const { depName, currentValue } = match!.groups!; const dep: PackageDependency = { depName, packageName: normalizePythonDepName(depName), currentValue, managerData: { lineNumber: token.line - 1, }, datasource: PypiDatasource.id, }; if (currentValue?.startsWith('==')) { dep.currentVersion = currentValue.replace(regEx(/^==\s*/), ''); } return { ...ctx, deps: [...ctx.deps, dep] }; } // Add `skip-reason` for dependencies annotated // with "# renovate: ignore" comment function depSkipHandler(ctx: Context): Context { const dep = ctx.deps.at(-1); const deps = ctx.deps.slice(0, -1); deps.push({ ...dep, skipReason: 'ignored' }); return { ...ctx, deps }; } const incompleteDepString = q .str(regEx(cleanupNamedGroups(depPattern))) .op(regEx(/^\+|\*$/)); const depString = q .str(regEx(cleanupNamedGroups(depPattern)), depStringHandler) .opt( q .opt(q.op(',')) .comment(regEx(/^#\s*renovate\s*:\s*ignore\s*$/), depSkipHandler), ); const query = q.alt(incompleteDepString, depString); export function extractPackageFile( content: string, _packageFile: string, _config: ExtractConfig, ): PackageFileContent | null { const res = python.query(content, query, { deps: [] }); return res?.deps?.length ? res : null; }