From 01a2d88d79a0b55a8b8c503e0a38a663574b6a94 Mon Sep 17 00:00:00 2001 From: stimjannik Date: Wed, 15 Oct 2025 21:33:00 +0200 Subject: [PATCH 1/3] feat(namespace-file-plugin): add namespace-file-plugin --- .../flowr-analyzer-namespace-file-plugin.ts | 27 +++++++ .../file-plugins/flowr-namespace-file.ts | 50 +++++++++++++ ...ackage-versions-description-file-plugin.ts | 2 +- ...-package-versions-namespace-file-plugin.ts | 33 +++++++++ .../package-version-plugins/package.ts | 12 +++- .../dependencies-query-executor.ts | 3 +- .../dependencies-query-format.ts | 7 +- src/util/files.ts | 72 +++++++++++++++++++ test/functionality/project/package.test.ts | 8 +-- .../project/plugin/namespace-file.test.ts | 49 +++++++++++++ 10 files changed, 252 insertions(+), 11 deletions(-) create mode 100644 src/project/plugins/file-plugins/flowr-analyzer-namespace-file-plugin.ts create mode 100644 src/project/plugins/file-plugins/flowr-namespace-file.ts create mode 100644 src/project/plugins/package-version-plugins/flowr-analyzer-package-versions-namespace-file-plugin.ts create mode 100644 test/functionality/project/plugin/namespace-file.test.ts diff --git a/src/project/plugins/file-plugins/flowr-analyzer-namespace-file-plugin.ts b/src/project/plugins/file-plugins/flowr-analyzer-namespace-file-plugin.ts new file mode 100644 index 00000000000..81ba65ba411 --- /dev/null +++ b/src/project/plugins/file-plugins/flowr-analyzer-namespace-file-plugin.ts @@ -0,0 +1,27 @@ +import { FlowrAnalyzerFilePlugin } from './flowr-analyzer-file-plugin'; +import { SemVer } from 'semver'; +import type { PathLike } from 'fs'; +import type { FlowrAnalyzerContext } from '../../context/flowr-analyzer-context'; +import type { FlowrFileProvider } from '../../context/flowr-file'; +import { SpecialFileRole } from '../../context/flowr-file'; +import { FlowrNamespaceFile } from './flowr-namespace-file'; + +/** + * This plugin provides support for R `NAMESPACE` files. + */ +export class FlowrAnalyzerNamespaceFilePlugin extends FlowrAnalyzerFilePlugin { + public readonly name = 'flowr-analyzer-namespace-file-plugin'; + public readonly description = 'This plugin provides support for NAMESPACE files and extracts their content into the NAMESPACEFormat.'; + public readonly version = new SemVer('0.1.0'); + + public applies(file: PathLike): boolean { + return /^(NAMESPACE|NAMESPACE\.txt)$/i.test(file.toString().split(/[/\\]/).pop() ?? ''); + } + + public process(_ctx: FlowrAnalyzerContext, file: FlowrFileProvider): FlowrNamespaceFile { + const f = FlowrNamespaceFile.from(file, SpecialFileRole.Namespace); + // already load it here + f.content(); + return f; + } +} \ No newline at end of file diff --git a/src/project/plugins/file-plugins/flowr-namespace-file.ts b/src/project/plugins/file-plugins/flowr-namespace-file.ts new file mode 100644 index 00000000000..8a0797c226e --- /dev/null +++ b/src/project/plugins/file-plugins/flowr-namespace-file.ts @@ -0,0 +1,50 @@ +import type { FlowrFileProvider, SpecialFileRole } from '../../context/flowr-file'; +import { FlowrFile } from '../../context/flowr-file'; +import { parseNAMESPACE } from '../../../util/files'; + +export interface NamespaceInfo { + exportedSymbols: string[]; + exportedFunctions: string[]; + exportS3Generics: Map; + loadsWithSideEffects: boolean; +} + +export interface NAMESPACEFormat { + this: NamespaceInfo; + [packageName: string]: NamespaceInfo; +} + +/** + * + */ +export class FlowrNamespaceFile extends FlowrFile { + private readonly wrapped: FlowrFileProvider; + + /** + * + */ + constructor(file: FlowrFileProvider) { + super(file.path(), file.role); + this.wrapped = file; + } + + /** + * + * + * @see {@link parseNAMESPACE} for details on the parsing logic. + */ + protected loadContent(): NAMESPACEFormat { + return parseNAMESPACE(this.wrapped); + } + + + /** + * Namespace file lifter, this does not re-create if already a namespace file + */ + public static from(file: FlowrFileProvider | FlowrNamespaceFile, role?: SpecialFileRole): FlowrNamespaceFile { + if(role) { + file.assignRole(role); + } + return file instanceof FlowrNamespaceFile ? file : new FlowrNamespaceFile(file); + } +} \ No newline at end of file diff --git a/src/project/plugins/package-version-plugins/flowr-analyzer-package-versions-description-file-plugin.ts b/src/project/plugins/package-version-plugins/flowr-analyzer-package-versions-description-file-plugin.ts index 20bf31ab309..be8178b89c9 100644 --- a/src/project/plugins/package-version-plugins/flowr-analyzer-package-versions-description-file-plugin.ts +++ b/src/project/plugins/package-version-plugins/flowr-analyzer-package-versions-description-file-plugin.ts @@ -42,7 +42,7 @@ export class FlowrAnalyzerPackageVersionsDescriptionFilePlugin extends FlowrAnal const [, name, operator, version] = match; const range = Package.parsePackageVersionRange(operator, version); - ctx.deps.addDependency(new Package(name, type, undefined, range)); + ctx.deps.addDependency(new Package(name, type, undefined, undefined, range)); } } } diff --git a/src/project/plugins/package-version-plugins/flowr-analyzer-package-versions-namespace-file-plugin.ts b/src/project/plugins/package-version-plugins/flowr-analyzer-package-versions-namespace-file-plugin.ts new file mode 100644 index 00000000000..36eaaa7aa58 --- /dev/null +++ b/src/project/plugins/package-version-plugins/flowr-analyzer-package-versions-namespace-file-plugin.ts @@ -0,0 +1,33 @@ +import { FlowrAnalyzerPackageVersionsPlugin } from './flowr-analyzer-package-versions-plugin'; +import { + descriptionFileLog +} from '../file-plugins/flowr-analyzer-description-file-plugin'; +import { SemVer } from 'semver'; +import { Package } from './package'; +import type { FlowrAnalyzerContext } from '../../context/flowr-analyzer-context'; +import { SpecialFileRole } from '../../context/flowr-file'; +import type { NAMESPACEFormat } from '../file-plugins/flowr-namespace-file'; + +/** + * + */ +export class FlowrAnalyzerPackageVersionsNamespaceFilePlugin extends FlowrAnalyzerPackageVersionsPlugin { + public readonly name = 'flowr-analyzer-package-version-namespace-file-plugin'; + public readonly description = 'This plugin does...'; + public readonly version = new SemVer('0.1.0'); + + process(ctx: FlowrAnalyzerContext): void { + const nmspcFiles = ctx.files.getFilesByRole(SpecialFileRole.Namespace); + if(nmspcFiles.length !== 1) { + descriptionFileLog.warn(`Supporting only exactly one NAMESPACE file, found ${nmspcFiles.length}`); + return; + } + + /** this will do the caching etc. for me */ + const deps = nmspcFiles[0].content() as NAMESPACEFormat; + + for(const pkg in deps) { + ctx.deps.addDependency(new Package(pkg, undefined, undefined, deps[pkg])); + } + } +} \ No newline at end of file diff --git a/src/project/plugins/package-version-plugins/package.ts b/src/project/plugins/package-version-plugins/package.ts index 02267dffe13..8af68b6d9b5 100644 --- a/src/project/plugins/package-version-plugins/package.ts +++ b/src/project/plugins/package-version-plugins/package.ts @@ -1,5 +1,6 @@ import { Range } from 'semver'; import { guard, isNotUndefined } from '../../../util/assert'; +import type { NamespaceInfo } from '../file-plugins/flowr-namespace-file'; export type PackageType = 'package' | 'system' | 'r'; @@ -8,11 +9,12 @@ export class Package { public derivedVersion?: Range; public type?: PackageType; public dependencies?: Package[]; + public namespaceInfo?: NamespaceInfo; public versionConstraints: Range[] = []; - constructor(name: string, type?: PackageType, dependencies?: Package[], ...versionConstraints: readonly (Range | undefined)[]) { + constructor(name: string, type?: PackageType, dependencies?: Package[], namespaceInfo?: NamespaceInfo, ...versionConstraints: readonly (Range | undefined)[]) { this.name = name; - this.addInfo(type, dependencies, ...(versionConstraints ?? []).filter(isNotUndefined)); + this.addInfo(type, dependencies, namespaceInfo, ...(versionConstraints ?? []).filter(isNotUndefined)); } public mergeInPlace(other: Package): void { @@ -20,17 +22,21 @@ export class Package { this.addInfo( other.type, other.dependencies, + other.namespaceInfo, ...other.versionConstraints ); } - public addInfo(type?: PackageType, dependencies?: Package[], ...versionConstraints: readonly Range[]): void { + public addInfo(type?: PackageType, dependencies?: Package[], namespaceInfo?: NamespaceInfo, ...versionConstraints: readonly Range[]): void { if(type !== undefined) { this.type = type; } if(dependencies !== undefined) { this.dependencies = dependencies; } + if(namespaceInfo !== undefined) { + this.namespaceInfo = namespaceInfo; + } if(versionConstraints !== undefined) { this.derivedVersion ??= versionConstraints[0]; diff --git a/src/queries/catalog/dependencies-query/dependencies-query-executor.ts b/src/queries/catalog/dependencies-query/dependencies-query-executor.ts index 3aeb4f71f91..b8c269cc93e 100644 --- a/src/queries/catalog/dependencies-query/dependencies-query-executor.ts +++ b/src/queries/catalog/dependencies-query/dependencies-query-executor.ts @@ -142,7 +142,8 @@ function getResults(queries: readonly DependenciesQuery[], { dataflow, config, n linkedIds: linked?.length ? linked : undefined, value: value ?? defaultValue, versionConstraints: dep?.versionConstraints, - derivedVersion: dep?.derivedVersion + derivedVersion: dep?.derivedVersion, + namespaceInfo: dep?.namespaceInfo, } as DependencyInfo); if(result) { results.push(result); diff --git a/src/queries/catalog/dependencies-query/dependencies-query-format.ts b/src/queries/catalog/dependencies-query/dependencies-query-format.ts index fd217559818..a5c2d7b48c8 100644 --- a/src/queries/catalog/dependencies-query/dependencies-query-format.ts +++ b/src/queries/catalog/dependencies-query/dependencies-query-format.ts @@ -16,6 +16,7 @@ import { RType } from '../../../r-bridge/lang-4.x/ast/model/type'; import type { CallContextQueryResult } from '../call-context-query/call-context-query-format'; import type { Range } from 'semver'; import type { AsyncOrSync } from 'ts-essentials'; +import type { NamespaceInfo } from '../../../project/plugins/file-plugins/flowr-namespace-file'; export const Unknown = 'unknown'; @@ -43,7 +44,8 @@ export const DefaultDependencyCategories = { functionName: (n.info.fullLexeme ?? n.lexeme).includes(':::') ? ':::' : '::', value: n.namespace, versionConstraints: dep?.versionConstraints, - derivedVersion: dep?.derivedVersion + derivedVersion: dep?.derivedVersion, + namespaceInfo: dep?.namespaceInfo }); } }); @@ -92,7 +94,8 @@ export interface DependencyInfo extends Record{ /** The library name, file, source, destination etc. being sourced, read from, or written to. */ value?: string versionConstraints?: Range[], - derivedVersion?: Range + derivedVersion?: Range, + namespaceInfo?: NamespaceInfo, } function printResultSection(title: string, infos: DependencyInfo[], result: string[]): void { diff --git a/src/util/files.ts b/src/util/files.ts index f24eff96ded..f7ec678b5de 100644 --- a/src/util/files.ts +++ b/src/util/files.ts @@ -5,6 +5,7 @@ import { log } from './log'; import LineByLine from 'n-readlines'; import type { RParseRequestFromFile } from '../r-bridge/retriever'; import type { FlowrFileProvider } from '../project/context/flowr-file'; +import type { NAMESPACEFormat } from '../project/plugins/file-plugins/flowr-namespace-file'; /** * Represents a table, identified by a header and a list of rows. @@ -217,7 +218,78 @@ export function parseDCF(file: FlowrFileProvider): Map return result; } +/** + * Parses the given NAMESPACE file + * @param file - The file to parse + * @returns + */ +export function parseNAMESPACE(file: FlowrFileProvider): NAMESPACEFormat { + const result = { + this: { + exportedSymbols: [] as string[], + exportedFunctions: [] as string[], + exportS3Generics: new Map(), + loadsWithSideEffects: false, + }, + } as NAMESPACEFormat; + const fileContent = file.content().replaceAll(cleanLineCommentRegex, '').trim() + .split(/\r?\n/).filter(Boolean); + + for(const line of fileContent) { + const match = line.trim().match(/^(\w+)\(([^)]*)\)$/); + if(!match) { + continue; + } + const [, type, args] = match; + + switch(type) { + case 'exportClasses': + case 'exportMethods': + result.this.exportedFunctions.push(args); + break; + case 'S3method': + { + const parts = args.split(',').map(s => s.trim()); + if(parts.length !== 2) { + continue; + } + const [pkg, func] = parts; + let arr = result.this.exportS3Generics.get(pkg); + if(!arr) { + arr = []; + result.this.exportS3Generics.set(pkg, arr); + } + arr.push(func); + break; + } + case 'export': + result.this.exportedSymbols.push(args); + break; + case 'useDynLib': + { + const parts = args.split(',').map(s => s.trim()); + if(parts.length !== 2) { + continue; + } + const [pkg, _func] = parts; + if(!result[pkg]) { + result[pkg] = { + exportedSymbols: [], + exportedFunctions: [], + exportS3Generics: new Map(), + loadsWithSideEffects: false, + }; + } + result[pkg].loadsWithSideEffects = true; + break; + } + } + } + + return result; +} +const cleanLineCommentRegex = /^#.*$/gm; const cleanSplitRegex = /[\n,]+/; const cleanQuotesRegex = /'/g; diff --git a/test/functionality/project/package.test.ts b/test/functionality/project/package.test.ts index 75156415abc..8405953f996 100644 --- a/test/functionality/project/package.test.ts +++ b/test/functionality/project/package.test.ts @@ -6,10 +6,10 @@ describe('DESCRIPTION-file', function() { describe.sequential('Parsing', function() { test('Library-Versions-Plugin', () => { const p1 = new Package('Test Package'); - p1.addInfo('package', undefined, new Range('>= 1.3')); - p1.addInfo(undefined, undefined, new Range('<= 2.3')); - p1.addInfo(undefined, undefined, new Range('>= 1.5')); - p1.addInfo(undefined, undefined, new Range('<= 2.2.5')); + p1.addInfo('package', undefined, undefined, new Range('>= 1.3')); + p1.addInfo(undefined, undefined, undefined, new Range('<= 2.3')); + p1.addInfo(undefined, undefined, undefined, new Range('>= 1.5')); + p1.addInfo(undefined, undefined, undefined, new Range('<= 2.2.5')); assert.isTrue(p1.derivedVersion?.test('1.7.0')); }); diff --git a/test/functionality/project/plugin/namespace-file.test.ts b/test/functionality/project/plugin/namespace-file.test.ts new file mode 100644 index 00000000000..d4ed6050b47 --- /dev/null +++ b/test/functionality/project/plugin/namespace-file.test.ts @@ -0,0 +1,49 @@ +import { assert, describe, test } from 'vitest'; +import path from 'path'; +import { FlowrAnalyzerContext } from '../../../../src/project/context/flowr-analyzer-context'; + + +import { arraysGroupBy } from '../../../../src/util/collections/arrays'; + + + + +import { FlowrInlineTextFile } from '../../../../src/project/context/flowr-file'; +import { + FlowrAnalyzerNamespaceFilePlugin +} from '../../../../src/project/plugins/file-plugins/flowr-analyzer-namespace-file-plugin'; +import { + FlowrAnalyzerPackageVersionsNamespaceFilePlugin +} from '../../../../src/project/plugins/package-version-plugins/flowr-analyzer-package-versions-namespace-file-plugin'; + + +describe('NAMESPACE-file', function() { + const ctx = new FlowrAnalyzerContext( + arraysGroupBy([ + new FlowrAnalyzerNamespaceFilePlugin(), + new FlowrAnalyzerPackageVersionsNamespaceFilePlugin() + ], p => p.type) + ); + + ctx.files.addFiles(new FlowrInlineTextFile(path.resolve('NAMESPACE'), `# Generated by roxygen2 (4.0.2): do not edit by hand +S3method(as.character,expectation) +S3method(compare,character) +export(auto_test) +export(auto_test_package) +export(colourise) +export(context) +exportClasses(ListReporter) +exportClasses(MinimalReporter) +importFrom(methods,setRefClass) +useDynLib(testthat,duplicate_) +useDynLib(testthat,reassign_function)`)); + ctx.files.addRequest({ request: 'file', content: 'pete.R' }); + ctx.resolvePreAnalysis(); + describe.sequential('Parsing', function() { + test('Library-Versions-Plugin', () => { + const deps = ctx.deps.getDependency('this'); + assert.isDefined(deps); + assert.isTrue(deps.namespaceInfo?.loadsWithSideEffects === false); + }); + }); +}); \ No newline at end of file From d62c6c3c7fd645402c87a3ba72d11144913c3693 Mon Sep 17 00:00:00 2001 From: stimjannik Date: Tue, 18 Nov 2025 16:04:56 +0100 Subject: [PATCH 2/3] wip(namespace-file-plugin): add functionsContext --- src/project/context/flowr-analyzer-context.ts | 10 +++- .../flowr-analyzer-functions-context.ts | 60 +++++++++++++++++++ .../flowr-analyzer-namespace-file-plugin.ts | 2 +- .../file-plugins/flowr-namespace-file.ts | 16 +++-- ...-package-versions-namespace-file-plugin.ts | 37 ++++++++++-- .../package-version-plugins/package.ts | 24 ++++++++ .../dependencies-query-executor.ts | 2 +- src/util/files.ts | 18 +++--- .../project/plugin/namespace-file.test.ts | 7 --- 9 files changed, 141 insertions(+), 35 deletions(-) create mode 100644 src/project/context/flowr-analyzer-functions-context.ts diff --git a/src/project/context/flowr-analyzer-context.ts b/src/project/context/flowr-analyzer-context.ts index b32d6842d69..ee6f9dda28c 100644 --- a/src/project/context/flowr-analyzer-context.ts +++ b/src/project/context/flowr-analyzer-context.ts @@ -17,6 +17,8 @@ import type { FlowrAnalyzerProjectDiscoveryPlugin } from '../plugins/project-discovery/flowr-analyzer-project-discovery-plugin'; import type { FlowrAnalyzerFilePlugin } from '../plugins/file-plugins/flowr-analyzer-file-plugin'; +import type { ReadOnlyFlowrAnalyzerFunctionsContext } from './flowr-analyzer-functions-context'; +import { FlowrAnalyzerFunctionsContext } from './flowr-analyzer-functions-context'; /** * This is a read-only interface to the {@link FlowrAnalyzerContext}. @@ -32,6 +34,8 @@ export interface ReadOnlyFlowrAnalyzerContext { * The dependencies context provides access to the identified dependencies and their versions. */ readonly deps: ReadOnlyFlowrAnalyzerDependenciesContext; + + readonly functions: ReadOnlyFlowrAnalyzerFunctionsContext; } /** @@ -47,14 +51,16 @@ export interface ReadOnlyFlowrAnalyzerContext { * If you are just interested in inspecting the context, you can use {@link ReadOnlyFlowrAnalyzerContext} instead (e.g., via {@link inspect}). */ export class FlowrAnalyzerContext implements ReadOnlyFlowrAnalyzerContext{ - public readonly files: FlowrAnalyzerFilesContext; - public readonly deps: FlowrAnalyzerDependenciesContext; + public readonly files: FlowrAnalyzerFilesContext; + public readonly deps: FlowrAnalyzerDependenciesContext; + public readonly functions: FlowrAnalyzerFunctionsContext; constructor(plugins: ReadonlyMap) { const loadingOrder = new FlowrAnalyzerLoadingOrderContext(this, plugins.get(PluginType.LoadingOrder) as FlowrAnalyzerLoadingOrderPlugin[]); this.files = new FlowrAnalyzerFilesContext(loadingOrder, (plugins.get(PluginType.ProjectDiscovery) ?? []) as FlowrAnalyzerProjectDiscoveryPlugin[], (plugins.get(PluginType.FileLoad) ?? []) as FlowrAnalyzerFilePlugin[]); this.deps = new FlowrAnalyzerDependenciesContext(this, (plugins.get(PluginType.DependencyIdentification) ?? []) as FlowrAnalyzerPackageVersionsPlugin[]); + this.functions = new FlowrAnalyzerFunctionsContext(this, (plugins.get(PluginType.DependencyIdentification) ?? []) as FlowrAnalyzerPackageVersionsPlugin[]); } /** delegate request addition */ diff --git a/src/project/context/flowr-analyzer-functions-context.ts b/src/project/context/flowr-analyzer-functions-context.ts new file mode 100644 index 00000000000..52ad17a46b8 --- /dev/null +++ b/src/project/context/flowr-analyzer-functions-context.ts @@ -0,0 +1,60 @@ +import { AbstractFlowrAnalyzerContext } from './abstract-flowr-analyzer-context'; +import type { FlowrAnalyzerContext } from './flowr-analyzer-context'; +import { + FlowrAnalyzerPackageVersionsPlugin +} from '../plugins/package-version-plugins/flowr-analyzer-package-versions-plugin'; + +export enum FunctionTypes { + Function = 'function', + Symbol = 'symbol', + S3 = 'S3' +} + +export interface FunctionInfo { + name: string; + packageOrigin: string; + isExported: boolean; + isS3Generic: boolean; + className?: string; + inferredType?: string; +} + +export interface ReadOnlyFlowrAnalyzerFunctionsContext { + readonly name: string; + + getFunctionInfo(name: string, className?: string): FunctionInfo | undefined; +} + +export class FlowrAnalyzerFunctionsContext extends AbstractFlowrAnalyzerContext implements ReadOnlyFlowrAnalyzerFunctionsContext { + public readonly name = 'flowr-analyzer-functions-context'; + + private readonly functionInfo: Map = new Map(); + + public constructor(ctx: FlowrAnalyzerContext, plugins?: readonly FlowrAnalyzerPackageVersionsPlugin[]) { + super(ctx, FlowrAnalyzerPackageVersionsPlugin.defaultPlugin(), plugins); + } + + public addFunctionInfo(functionInfo: FunctionInfo) { + const fi = this.functionInfo.get(functionInfo.name); + if(fi) { + // merge? + } else { + this.functionInfo.set(functionInfo.name, functionInfo); + } + } + + public getFunctionInfo(name: string, className?: string): FunctionInfo | undefined { + if(className) { + return this.functionInfo.get(`${name}.${className}`); + } else if(name.includes('.')){ + const parts = name.split('.'); + const splitClassName = parts.pop(); + const splitName = parts.join('.'); + if(this.functionInfo.has(splitName)) { + const value = this.functionInfo.get(splitName); + return value?.className === splitClassName ? value : undefined; + } + } + return this.functionInfo.get(name); + } +} \ No newline at end of file diff --git a/src/project/plugins/file-plugins/flowr-analyzer-namespace-file-plugin.ts b/src/project/plugins/file-plugins/flowr-analyzer-namespace-file-plugin.ts index 81ba65ba411..28f3ed1e49a 100644 --- a/src/project/plugins/file-plugins/flowr-analyzer-namespace-file-plugin.ts +++ b/src/project/plugins/file-plugins/flowr-analyzer-namespace-file-plugin.ts @@ -15,7 +15,7 @@ export class FlowrAnalyzerNamespaceFilePlugin extends FlowrAnalyzerFilePlugin { public readonly version = new SemVer('0.1.0'); public applies(file: PathLike): boolean { - return /^(NAMESPACE|NAMESPACE\.txt)$/i.test(file.toString().split(/[/\\]/).pop() ?? ''); + return /^(NAMESPACE(\.txt)?)$/i.test(file.toString().split(/[/\\]/).pop() ?? ''); } public process(_ctx: FlowrAnalyzerContext, file: FlowrFileProvider): FlowrNamespaceFile { diff --git a/src/project/plugins/file-plugins/flowr-namespace-file.ts b/src/project/plugins/file-plugins/flowr-namespace-file.ts index 8a0797c226e..5ae2fd52cd9 100644 --- a/src/project/plugins/file-plugins/flowr-namespace-file.ts +++ b/src/project/plugins/file-plugins/flowr-namespace-file.ts @@ -1,6 +1,6 @@ import type { FlowrFileProvider, SpecialFileRole } from '../../context/flowr-file'; import { FlowrFile } from '../../context/flowr-file'; -import { parseNAMESPACE } from '../../../util/files'; +import { parseNamespace } from '../../../util/files'; export interface NamespaceInfo { exportedSymbols: string[]; @@ -9,15 +9,15 @@ export interface NamespaceInfo { loadsWithSideEffects: boolean; } -export interface NAMESPACEFormat { - this: NamespaceInfo; +export interface NamespaceFormat { + main: NamespaceInfo; [packageName: string]: NamespaceInfo; } /** * */ -export class FlowrNamespaceFile extends FlowrFile { +export class FlowrNamespaceFile extends FlowrFile { private readonly wrapped: FlowrFileProvider; /** @@ -29,12 +29,10 @@ export class FlowrNamespaceFile extends FlowrFile { } /** - * - * - * @see {@link parseNAMESPACE} for details on the parsing logic. + * @see {@link parseNamespace} for details on the parsing logic. */ - protected loadContent(): NAMESPACEFormat { - return parseNAMESPACE(this.wrapped); + protected loadContent(): NamespaceFormat { + return parseNamespace(this.wrapped); } diff --git a/src/project/plugins/package-version-plugins/flowr-analyzer-package-versions-namespace-file-plugin.ts b/src/project/plugins/package-version-plugins/flowr-analyzer-package-versions-namespace-file-plugin.ts index 36eaaa7aa58..0def280740a 100644 --- a/src/project/plugins/package-version-plugins/flowr-analyzer-package-versions-namespace-file-plugin.ts +++ b/src/project/plugins/package-version-plugins/flowr-analyzer-package-versions-namespace-file-plugin.ts @@ -6,11 +6,8 @@ import { SemVer } from 'semver'; import { Package } from './package'; import type { FlowrAnalyzerContext } from '../../context/flowr-analyzer-context'; import { SpecialFileRole } from '../../context/flowr-file'; -import type { NAMESPACEFormat } from '../file-plugins/flowr-namespace-file'; +import type { NamespaceFormat } from '../file-plugins/flowr-namespace-file'; -/** - * - */ export class FlowrAnalyzerPackageVersionsNamespaceFilePlugin extends FlowrAnalyzerPackageVersionsPlugin { public readonly name = 'flowr-analyzer-package-version-namespace-file-plugin'; public readonly description = 'This plugin does...'; @@ -24,10 +21,38 @@ export class FlowrAnalyzerPackageVersionsNamespaceFilePlugin extends FlowrAnalyz } /** this will do the caching etc. for me */ - const deps = nmspcFiles[0].content() as NAMESPACEFormat; + const deps = nmspcFiles[0].content() as NamespaceFormat; for(const pkg in deps) { - ctx.deps.addDependency(new Package(pkg, undefined, undefined, deps[pkg])); + const info = deps[pkg]; + ctx.deps.addDependency(new Package(pkg, undefined, undefined, info)); + for(const exportedSymbol of info.exportedSymbols) { + ctx.functions.addFunctionInfo({ + name: exportedSymbol, + packageOrigin: pkg, + isExported: true, + isS3Generic: false, + }); + } + for(const exportedFunction of info.exportedFunctions) { + ctx.functions.addFunctionInfo({ + name: exportedFunction, + packageOrigin: pkg, + isExported: true, + isS3Generic: false, + }); + } + for(const [genericName, classes] of info.exportS3Generics.entries()) { + for(const className of classes) { + ctx.functions.addFunctionInfo({ + name: genericName, + packageOrigin: pkg, + isExported: true, + isS3Generic: true, + className: className, + }); + } + } } } } \ No newline at end of file diff --git a/src/project/plugins/package-version-plugins/package.ts b/src/project/plugins/package-version-plugins/package.ts index 8af68b6d9b5..c740cd03eab 100644 --- a/src/project/plugins/package-version-plugins/package.ts +++ b/src/project/plugins/package-version-plugins/package.ts @@ -17,6 +17,30 @@ export class Package { this.addInfo(type, dependencies, namespaceInfo, ...(versionConstraints ?? []).filter(isNotUndefined)); } + has(name: string, className?: string): boolean { + if(!this.namespaceInfo) { + return false; + } + + if(name.includes('.')) { + const [genericSplit, classSplit] = name.split('.'); + const classes = this.namespaceInfo.exportS3Generics.get(genericSplit); + return classes ? classes.includes(classSplit) : false; + } + + if(className) { + const classes = this.namespaceInfo.exportS3Generics.get(name); + return classes ? classes.includes(className) : false; + } + + return this.namespaceInfo.exportedFunctions.includes(name) || this.namespaceInfo.exportedSymbols.includes(name); + } + + s3For(generic: string): string[] { + return this.namespaceInfo?.exportS3Generics.get(generic) ?? []; + } + + public mergeInPlace(other: Package): void { guard(this.name === other.name, 'Can only merge packages with the same name'); this.addInfo( diff --git a/src/queries/catalog/dependencies-query/dependencies-query-executor.ts b/src/queries/catalog/dependencies-query/dependencies-query-executor.ts index b8c269cc93e..71eefe1141f 100644 --- a/src/queries/catalog/dependencies-query/dependencies-query-executor.ts +++ b/src/queries/catalog/dependencies-query/dependencies-query-executor.ts @@ -143,7 +143,7 @@ function getResults(queries: readonly DependenciesQuery[], { dataflow, config, n value: value ?? defaultValue, versionConstraints: dep?.versionConstraints, derivedVersion: dep?.derivedVersion, - namespaceInfo: dep?.namespaceInfo, + namespaceInfo: dep?.namespaceInfo, } as DependencyInfo); if(result) { results.push(result); diff --git a/src/util/files.ts b/src/util/files.ts index f7ec678b5de..d4af224be47 100644 --- a/src/util/files.ts +++ b/src/util/files.ts @@ -5,7 +5,7 @@ import { log } from './log'; import LineByLine from 'n-readlines'; import type { RParseRequestFromFile } from '../r-bridge/retriever'; import type { FlowrFileProvider } from '../project/context/flowr-file'; -import type { NAMESPACEFormat } from '../project/plugins/file-plugins/flowr-namespace-file'; +import type { NamespaceFormat } from '../project/plugins/file-plugins/flowr-namespace-file'; /** * Represents a table, identified by a header and a list of rows. @@ -223,15 +223,15 @@ export function parseDCF(file: FlowrFileProvider): Map * @param file - The file to parse * @returns */ -export function parseNAMESPACE(file: FlowrFileProvider): NAMESPACEFormat { +export function parseNamespace(file: FlowrFileProvider): NamespaceFormat { const result = { - this: { + main: { exportedSymbols: [] as string[], exportedFunctions: [] as string[], exportS3Generics: new Map(), loadsWithSideEffects: false, }, - } as NAMESPACEFormat; + } as NamespaceFormat; const fileContent = file.content().replaceAll(cleanLineCommentRegex, '').trim() .split(/\r?\n/).filter(Boolean); @@ -245,7 +245,7 @@ export function parseNAMESPACE(file: FlowrFileProvider): NAMESPACEFormat switch(type) { case 'exportClasses': case 'exportMethods': - result.this.exportedFunctions.push(args); + result.main.exportedFunctions.push(args); break; case 'S3method': { @@ -254,16 +254,16 @@ export function parseNAMESPACE(file: FlowrFileProvider): NAMESPACEFormat continue; } const [pkg, func] = parts; - let arr = result.this.exportS3Generics.get(pkg); + let arr = result.main.exportS3Generics.get(pkg); if(!arr) { arr = []; - result.this.exportS3Generics.set(pkg, arr); + result.main.exportS3Generics.set(pkg, arr); } arr.push(func); break; } case 'export': - result.this.exportedSymbols.push(args); + result.main.exportedSymbols.push(args); break; case 'useDynLib': { @@ -271,7 +271,7 @@ export function parseNAMESPACE(file: FlowrFileProvider): NAMESPACEFormat if(parts.length !== 2) { continue; } - const [pkg, _func] = parts; + const [pkg] = parts; if(!result[pkg]) { result[pkg] = { exportedSymbols: [], diff --git a/test/functionality/project/plugin/namespace-file.test.ts b/test/functionality/project/plugin/namespace-file.test.ts index d4ed6050b47..388269dd4c4 100644 --- a/test/functionality/project/plugin/namespace-file.test.ts +++ b/test/functionality/project/plugin/namespace-file.test.ts @@ -1,13 +1,7 @@ import { assert, describe, test } from 'vitest'; import path from 'path'; import { FlowrAnalyzerContext } from '../../../../src/project/context/flowr-analyzer-context'; - - import { arraysGroupBy } from '../../../../src/util/collections/arrays'; - - - - import { FlowrInlineTextFile } from '../../../../src/project/context/flowr-file'; import { FlowrAnalyzerNamespaceFilePlugin @@ -16,7 +10,6 @@ import { FlowrAnalyzerPackageVersionsNamespaceFilePlugin } from '../../../../src/project/plugins/package-version-plugins/flowr-analyzer-package-versions-namespace-file-plugin'; - describe('NAMESPACE-file', function() { const ctx = new FlowrAnalyzerContext( arraysGroupBy([ From 3717b1e0d3d3a2a9abcebc6ad8015d2a9a143f12 Mon Sep 17 00:00:00 2001 From: stimjannik Date: Tue, 18 Nov 2025 16:07:45 +0100 Subject: [PATCH 3/3] lint-fix(namespace-file-plugin): fix broken space --- .../catalog/dependencies-query/dependencies-query-executor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/queries/catalog/dependencies-query/dependencies-query-executor.ts b/src/queries/catalog/dependencies-query/dependencies-query-executor.ts index 71eefe1141f..b59b67fe3ed 100644 --- a/src/queries/catalog/dependencies-query/dependencies-query-executor.ts +++ b/src/queries/catalog/dependencies-query/dependencies-query-executor.ts @@ -143,7 +143,7 @@ function getResults(queries: readonly DependenciesQuery[], { dataflow, config, n value: value ?? defaultValue, versionConstraints: dep?.versionConstraints, derivedVersion: dep?.derivedVersion, - namespaceInfo: dep?.namespaceInfo, + namespaceInfo: dep?.namespaceInfo } as DependencyInfo); if(result) { results.push(result);