Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/project/context/flowr-analyzer-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,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';
import { arraysGroupBy } from '../../util/collections/arrays';
import type { fileProtocol, RParseRequestFromFile, RParseRequests } from '../../r-bridge/retriever';
import { requestFromInput } from '../../r-bridge/retriever';
Expand All @@ -45,6 +47,8 @@ export interface ReadOnlyFlowrAnalyzerContext {
* The configuration options used by the analyzer.
*/
readonly config: FlowrConfigOptions;

readonly functions: ReadOnlyFlowrAnalyzerFunctionsContext;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a doc comment

}

/**
Expand All @@ -60,16 +64,19 @@ 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 config: FlowrConfigOptions;
public readonly files: FlowrAnalyzerFilesContext;
public readonly deps: FlowrAnalyzerDependenciesContext;
public readonly config: FlowrConfigOptions;
public readonly functions: FlowrAnalyzerFunctionsContext;


constructor(config: FlowrConfigOptions, plugins: ReadonlyMap<PluginType, readonly FlowrAnalyzerPlugin[]>) {
this.config = config;
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[]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The functions context should not directly rely on the package versions, but maybe be a child of the analyzer dependencies context similar to the loading order

}

/** delegate request addition */
Expand Down
64 changes: 64 additions & 0 deletions src/project/context/flowr-analyzer-functions-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is weird to have a function type symbol, maybe rename this to ExportTypes

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the function info must be scoped by the package namespace!

}

export class FlowrAnalyzerFunctionsContext extends AbstractFlowrAnalyzerContext<undefined, void, FlowrAnalyzerPackageVersionsPlugin> implements ReadOnlyFlowrAnalyzerFunctionsContext {
public readonly name = 'flowr-analyzer-functions-context';

private functionInfo: Map<string, FunctionInfo> = new Map<string, FunctionInfo>();

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?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and at least warn

} else {
this.functionInfo.set(functionInfo.name, functionInfo);
}
}

public getFunctionInfo(name: string, className?: string): FunctionInfo | undefined {
if(className) {
return this.functionInfo.get(`${name}.${className}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not what i expected with className maybe s3TypeDispatch?

} 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);
}

public reset(): void {
this.functionInfo = new Map<string, FunctionInfo>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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 { FileRole } from '../../context/flowr-file';
import { FlowrNamespaceFile } from './flowr-namespace-file';
import { platformBasename } from '../../../dataflow/internal/process/functions/call/built-in/built-in-source';

const NamespaceFilePattern = /^(NAMESPACE(\.txt)?)$/i;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think we need the outer parens


/**
* 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');
private readonly pattern: RegExp;

/**
* Creates a new instance of the NAMESPACE file plugin.
* @param filePattern - The pattern to identify NAMESPACE files, see {@link NamespaceFilePattern} for the default pattern.
*/
constructor(filePattern: RegExp = NamespaceFilePattern) {
super();
this.pattern = filePattern;
}

public applies(file: PathLike): boolean {
return this.pattern.test(platformBasename(file.toString()));
}

public process(_ctx: FlowrAnalyzerContext, file: FlowrFileProvider): FlowrNamespaceFile {
return FlowrNamespaceFile.from(file, FileRole.Namespace);
}
}
48 changes: 48 additions & 0 deletions src/project/plugins/file-plugins/flowr-namespace-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { FileRole, FlowrFileProvider } from '../../context/flowr-file';
import { FlowrFile } from '../../context/flowr-file';
import { parseNamespace } from '../../../util/files';

export interface NamespaceInfo {
exportedSymbols: string[];
exportedFunctions: string[];
exportS3Generics: Map<string, string[]>;
loadsWithSideEffects: boolean;
}

export interface NamespaceFormat {
current: NamespaceInfo;
[packageName: string]: NamespaceInfo;
}

/**
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you please fill in these?

*/
export class FlowrNamespaceFile extends FlowrFile<NamespaceFormat> {
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?: FileRole): FlowrNamespaceFile {
if(role) {
file.assignRole(role);
}
return file instanceof FlowrNamespaceFile ? file : new FlowrNamespaceFile(file);
}
}
2 changes: 2 additions & 0 deletions src/project/plugins/flowr-analyzer-plugin-defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { FlowrAnalyzerRmdFilePlugin } from './file-plugins/notebooks/flowr-analyzer-rmd-file-plugin';
import { FlowrAnalyzerQmdFilePlugin } from './file-plugins/notebooks/flowr-analyzer-qmd-file-plugin';
import { FlowrAnalyzerJupyterFilePlugin } from './file-plugins/notebooks/flowr-analyzer-jupyter-file-plugin';
import { FlowrAnalyzerNamespaceFilePlugin } from './file-plugins/flowr-analyzer-namespace-file-plugin';

/**
* Provides the default set of Flowr Analyzer plugins.
Expand All @@ -21,5 +22,6 @@ export function FlowrAnalyzerPluginDefaults(): FlowrAnalyzerPlugin[] {
new FlowrAnalyzerRmdFilePlugin(),
new FlowrAnalyzerQmdFilePlugin(),
new FlowrAnalyzerJupyterFilePlugin(),
new FlowrAnalyzerNamespaceFilePlugin()
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ 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: name,
type: type,
versionConstraints: range ? [range] : undefined
}
));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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 { FileRole } 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(FileRole.Namespace);
if(nmspcFiles.length !== 1) {
descriptionFileLog.warn(`Supporting only exactly one NAMESPACE file, found ${nmspcFiles.length}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we? why? namespace files can be merged, we just have to throw errors if things are incompatible, but we have to that as well if it happens in the same namespace file

return;
}

/** this will do the caching etc. for me */
const deps = nmspcFiles[0].content() as NamespaceFormat;

for(const pkg in deps) {
const info = deps[pkg];
ctx.deps.addDependency(new Package(
{
name: pkg,
namespaceInfo: 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,
});
}
}
}
}
}
62 changes: 54 additions & 8 deletions src/project/plugins/package-version-plugins/package.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,82 @@
import { Range } from 'semver';
import { guard, isNotUndefined } from '../../../util/assert';
import { guard } from '../../../util/assert';
import type { NamespaceInfo } from '../file-plugins/flowr-namespace-file';

export type PackageType = 'package' | 'system' | 'r';

export type PackageOptions = {
derivedVersion?: Range;
type?: PackageType;
dependencies?: Package[];
namespaceInfo?: NamespaceInfo;
versionConstraints?: Range[];
}

export class Package {
public name: string;
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)[]) {
this.name = name;
this.addInfo(type, dependencies, ...(versionConstraints ?? []).filter(isNotUndefined));
constructor(info: { name: string } & PackageOptions) {
this.name = info.name;
this.addInfo(info);
}

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(
other.type,
other.dependencies,
...other.versionConstraints
{
type: other.type,
dependencies: other.dependencies,
namespaceInfo: other.namespaceInfo,
versionConstraints: other.versionConstraints
}
);
}

public addInfo(type?: PackageType, dependencies?: Package[], ...versionConstraints: readonly Range[]): void {
public addInfo(info: PackageOptions): void {
const {
type,
dependencies,
namespaceInfo,
versionConstraints
} = info;

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];

Expand Down
2 changes: 2 additions & 0 deletions src/project/plugins/plugin-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { FlowrAnalyzerRmdFilePlugin } from './file-plugins/notebooks/flowr-analy
import { FlowrAnalyzerQmdFilePlugin } from './file-plugins/notebooks/flowr-analyzer-qmd-file-plugin';
import { guard } from '../../util/assert';
import { FlowrAnalyzerJupyterFilePlugin } from './file-plugins/notebooks/flowr-analyzer-jupyter-file-plugin';
import { FlowrAnalyzerNamespaceFilePlugin } from './file-plugins/flowr-analyzer-namespace-file-plugin';

/**
* The built-in Flowr Analyzer plugins that are always available.
Expand All @@ -21,6 +22,7 @@ export const BuiltInPlugins = [
['file:rmd', FlowrAnalyzerRmdFilePlugin],
['file:qmd', FlowrAnalyzerQmdFilePlugin],
['file:ipynb', FlowrAnalyzerJupyterFilePlugin],
['file:namespace', FlowrAnalyzerNamespaceFilePlugin],
] as const satisfies [string, PluginProducer][];

export type BuiltInFlowrPluginName = typeof BuiltInPlugins[number][0];
Expand Down
Loading
Loading