Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c097974
feat(lint): add structure for deprectaed args - #1870
gigalasr Jul 9, 2026
9116b6e
feat(lint): only detect when deprectaed arg is present - #1870
gigalasr Jul 9, 2026
05e92b8
feat(lint): allow new metadata in functions as well - #1870
gigalasr Jul 10, 2026
a43d0bc
feat(lint): pretty print new deprecation info - #1870
gigalasr Jul 10, 2026
493606b
feat(lint): use source location of arg - #1870
gigalasr Jul 10, 2026
5efa7cc
Merge remote-tracking branch 'origin/main' into 1870-deprecated-args-…
gigalasr Jul 10, 2026
e59b179
Merge remote-tracking branch 'origin/main' into 1870-deprecated-args-…
gigalasr Jul 16, 2026
b4a79ba
feat-fix(lint): use new RRange util - #1870
gigalasr Jul 16, 2026
ae5965b
docs(eval): describe interface and deprecation state - #1870
gigalasr Jul 16, 2026
314adc7
feat(dep): cleaner config - #1870
gigalasr Jul 23, 2026
1172cd3
Merge remote-tracking branch 'origin/main' into 1870-deprecated-args-…
gigalasr Jul 23, 2026
2c89bc7
feat(dep): support for version checking - #1870
gigalasr Jul 24, 2026
118a07d
Merge remote-tracking branch 'origin/main' into 1870-deprecated-args-…
gigalasr Jul 24, 2026
6f0049e
feat(dep): version check for functions #1870
gigalasr Jul 24, 2026
e36782b
feat(lint): ifValue support for deprecated args - #1870
gigalasr Jul 24, 2026
7a80a2a
Merge remote-tracking branch 'origin/main' into 1870-deprecated-args-…
gigalasr Aug 6, 2026
6bb30fc
lint(linter): eslint fixes - #1870
gigalasr Aug 6, 2026
fa811e5
feat(lint): use identifier - #1870
gigalasr Aug 6, 2026
ae6c74f
feat(lint): try to fix derrived range - #1870
gigalasr Aug 7, 2026
f69a89b
Merge remote-tracking branch 'origin/main' into 1870-deprecated-args-…
gigalasr Aug 7, 2026
8d9cbfd
feat(depr): use guess-dep querry - #1870
gigalasr Aug 7, 2026
f05e5e0
Merge remote-tracking branch 'origin/main' into 1870-deprecated-args-…
gigalasr Aug 7, 2026
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
132 changes: 124 additions & 8 deletions src/linter/rules/deprecated-functions.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,135 @@
import { LintingRuleCertainty, type LintingRule } from '../linter-format';
import type { BrandedIdentifier } from '../../dataflow/environments/identifier';
import { FunctionArgument } from '../../dataflow/graph/graph';
import { isFunctionCallVertex } from '../../dataflow/graph/vertex';
import { Enrichment, enrichmentContent } from '../../search/search-executor/search-enrichers';
import { isNotUndefined } from '../../util/assert';
import { SourceLocation } from '../../util/range';
import { LintingResultCertainty, LintingRuleCertainty, type LintingRule } from '../linter-format';
import { LintingRuleTag } from '../linter-tags';
import { type FunctionsMetadata, type FunctionsResult, type FunctionsToDetectConfig, functionFinderUtil } from './function-finder-util';

/**
* Information about an argument of a function that should be flagged as deprected if it is called with this argument
*/
export interface DeprecatedArgumentInformation {
argIdx?: number,
argName?: string
ifValue?: RegExp | string
replacedBy?: string
version?: string
state: DeprecationState
}

export const enum DeprecationState {
/**
* Still works, but marked for removal
*/
Deprecated,
/**
* No longer works, and marked for removal
*/
Defunct,
/**
* Replaced by another function but kept for compatibility
*/
Superseeded
}

export interface DeprecatedFunctionsConfig<Fns extends readonly string[] = readonly string[]> extends FunctionsToDetectConfig {
/**
* Functions to mark as deprecated
*/
fns: Fns
/**
* Additionally, constraint the detection of an fn in {@link DeprecatedFunctionsConfig.fns} if it is called with an Argument
* defined by {@link DeprecatedFunctionsConfig.whenArgs}
*/
whenArgs: Partial<Record<Fns[number], DeprecatedArgumentInformation[]>>
}
// Little helper for constraining whenArgs to fns
const defineConfig = <const Fns extends readonly string[]>(config: DeprecatedFunctionsConfig<Fns>) => config;
Comment thread
gigalasr marked this conversation as resolved.
Outdated

export const DEPRECATED_FUNCTIONS = {
createSearch: (config) => functionFinderUtil.createSearch(config.fns),
processSearchResult: functionFinderUtil.processSearchResult,
prettyPrint: functionFinderUtil.prettyPrint('deprecated'),
info: {
processSearchResult: async(elements, config, data) => {
const dataflow = (await data.dataflow()).graph;
const metadata: FunctionsMetadata = {
totalCalls: 0,
totalFunctionDefinitions: 0
};

const results = elements.getElements().flatMap(e => {
metadata.totalCalls++;
return enrichmentContent(e, Enrichment.CallTargets).targets.map(target => {
metadata.totalFunctionDefinitions++;
return {
node: e.node,
loc: SourceLocation.fromNode(e.node),
target: target as BrandedIdentifier,
};
});
}).filter(e => isNotUndefined(e.loc));

// Filter out functions that are only deprecated when a certain Argument is present
const resultsWhenArg = results.flatMap(r => {
// If not provided in whenArgs, it is always deprecated
const whenArgs = config.whenArgs[r.target];
if(whenArgs === undefined) {
return r;
}

// Check if function has deprecated args
const deprecatedArgs = whenArgs.map(depricationInfo => {
const vertex = dataflow.getVertex(r.node.info.id);
if(vertex === undefined || !isFunctionCallVertex(vertex)) {
return undefined;
}

if(vertex.args.some((arg, idx) =>
FunctionArgument.isNamed(arg) && arg.name === depricationInfo.argName ||
FunctionArgument.isPositional(arg) && idx === depricationInfo.argIdx
)) {
return {
arg: depricationInfo.argName ?? depricationInfo.argIdx,
state: depricationInfo.state,
replacedBy: depricationInfo.replacedBy
};
}
}).filter(p => p !== undefined);

// Don't mark function as deprecated, if we didn't find any deprecated args
return deprecatedArgs.length === 0 ? undefined : {
...r,
deprecatedArgs: deprecatedArgs
};
}).filter(p => p !== undefined);

return {
results: resultsWhenArg.map(e => ({
certainty: LintingResultCertainty.Certain,
involvedId: e.node.info.id,
function: e.target,
loc: e.loc
})) as FunctionsResult[],
'.meta': metadata
};
},
prettyPrint: functionFinderUtil.prettyPrint('deprecated'),
info: {
name: 'Deprecated Functions',
tags: [LintingRuleTag.Deprecated, LintingRuleTag.Smell, LintingRuleTag.Usability, LintingRuleTag.Reproducibility],
// ensures all deprecated functions found are actually deprecated through its limited config, but doesn't find all deprecated functions since the config is pre-crawled
certainty: LintingRuleCertainty.BestEffort,
description: 'Marks deprecated functions that should not be used anymore.',
defaultConfig: {
fns: ['all_equal', 'arrange_all', 'distinct_all', 'filter_all', 'group_by_all', 'summarise_all', 'mutate_all', 'select_all', 'vars', 'all_vars', 'id', 'failwith', 'select_vars', 'rename_vars', 'select_var', 'current_vars', 'bench_tbls', 'compare_tbls', 'compare_tbls2', 'eval_tbls', 'eval_tbls2', 'location', 'changes', 'combine', 'do', 'funs', 'add_count_', 'add_tally_', 'arrange_', 'count_', 'distinct_', 'do_', 'filter_', 'funs_', 'group_by_', 'group_indices_', 'mutate_', 'tally_', 'transmute_', 'rename_', 'rename_vars_', 'select_', 'select_vars_', 'slice_', 'summarise_', 'summarize_', 'summarise_each', 'src_local', 'tbl_df', 'add_rownames', 'group_nest', 'group_split', 'with_groups', 'nest_by', 'progress_estimated', 'recode', 'sample_n', 'top_n', 'transmute', 'fct_explicit_na', 'aes_', 'aes_auto', 'annotation_logticks', 'is.Coord', 'coord_flip', 'coord_map', 'is.facet', 'fortify', 'is.ggproto', 'guide_train', 'is.ggplot', 'qplot', 'is.theme', 'gg_dep', 'liply', 'isplit2', 'list_along', 'cross', 'invoke', 'at_depth', 'prepend', 'rerun', 'splice', '`%@%`', 'rbernoulli', 'rdunif', 'when', 'update_list', 'map_raw', 'accumulate', 'reduce_right', 'flatten', 'map_dfr', 'as_vector', 'transpose', 'melt_delim', 'melt_fwf', 'melt_table', 'read_table2', 'str_interp', 'as_tibble', 'data_frame', 'tibble_', 'data_frame_', 'lst_', 'as_data_frame', 'as.tibble', 'frame_data', 'trunc_mat', 'is.tibble', 'tidy_names', 'set_tidy_names', 'repair_names', 'extract_numeric', 'complete_', 'drop_na_', 'expand_', 'crossing_', 'nesting_', 'extract_', 'fill_', 'gather_', 'nest_', 'separate_rows_', 'separate_', 'spread_', 'unite_', 'unnest_', 'extract', 'gather', 'nest_legacy', 'separate_rows', 'separate', 'spread']
}
defaultConfig: defineConfig({
fns: ['geom_violin', 'all_equal', 'arrange_all', 'distinct_all', 'filter_all', 'group_by_all', 'summarise_all', 'mutate_all', 'select_all', 'vars', 'all_vars', 'id', 'failwith', 'select_vars', 'rename_vars', 'select_var', 'current_vars', 'bench_tbls', 'compare_tbls', 'compare_tbls2', 'eval_tbls', 'eval_tbls2', 'location', 'changes', 'combine', 'do', 'funs', 'add_count_', 'add_tally_', 'arrange1_', 'count_', 'distinct_', 'do_', 'filter_', 'funs_', 'group_by_', 'group_indices_', 'mutate_', 'tally_', 'transmute_', 'rename_', 'rename_vars_', 'select_', 'select_vars_', 'slice_', 'summarise_', 'summarize_', 'summarise_each', 'src_local', 'tbl_df', 'add_rownames', 'group_nest', 'group_split', 'with_groups', 'nest_by', 'progress_estimated', 'recode', 'sample_n', 'top_n', 'transmute', 'fct_explicit_na', 'aes_', 'aes_auto', 'annotation_logticks', 'is.Coord', 'coord_flip', 'coord_map', 'is.facet', 'fortify', 'is.ggproto', 'guide_train', 'is.ggplot', 'qplot', 'is.theme', 'gg_dep', 'liply', 'isplit2', 'list_along', 'cross', 'invoke', 'at_depth', 'prepend', 'rerun', 'splice', '`%@%`', 'rbernoulli', 'rdunif', 'when', 'update_list', 'map_raw', 'accumulate', 'reduce_right', 'flatten', 'map_dfr', 'as_vector', 'transpose', 'melt_delim', 'melt_fwf', 'melt_table', 'read_table2', 'str_interp', 'as_tibble', 'data_frame', 'tibble_', 'data_frame_', 'lst_', 'as_data_frame', 'as.tibble', 'frame_data', 'trunc_mat', 'is.tibble', 'tidy_names', 'set_tidy_names', 'repair_names', 'extract_numeric', 'complete_', 'drop_na_', 'expand_', 'crossing_', 'nesting_', 'extract_', 'fill_', 'gather_', 'nest_', 'separate_rows_', 'separate_', 'spread_', 'unite_', 'unnest_', 'extract', 'gather', 'nest_legacy', 'separate_rows', 'separate', 'spread'],
whenArgs: {
'geom_violin': [{
argName: 'draw_quantiles',
state: DeprecationState.Deprecated,
replacedBy: 'quantile.linetype'
}]
}
})
}
} as const satisfies LintingRule<FunctionsResult, FunctionsMetadata, FunctionsToDetectConfig>;
} as const satisfies LintingRule<FunctionsResult, FunctionsMetadata, DeprecatedFunctionsConfig>;
2 changes: 1 addition & 1 deletion src/linter/rules/naming-convention.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export function getMostUsedCasing(symbols: { detectedCasing: CasingConvention }[
map.set(symbol.detectedCasing, o + 1);
}

// Return element with most occurances
// Return element with most occurrences
return [...map].reduce((p, c) => p[1] > c[1] ? p : c)[0];
}

Expand Down
8 changes: 3 additions & 5 deletions src/linter/rules/network-functions.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { LintingResultCertainty, type LintingRule, LintingRuleCertainty } from '../linter-format';
import { functionFinderUtil, type FunctionsMetadata, type FunctionsResult } from './function-finder-util';
import type { FunctionsToDetectConfig, FunctionsMetadata, FunctionsResult } from './function-finder-util';
import { functionFinderUtil } from './function-finder-util';
import { LintingRuleTag } from '../linter-tags';
import type { MergeableRecord } from '../../util/objects';
import { ReadFunctions } from '../../queries/catalog/dependencies-query/function-info/read-functions';
import type { FlowrSearchElement } from '../../search/flowr-search';
import type { ParentInformation } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
import { Ternary } from '../../util/logic';
import { SourceFunctions } from '../../queries/catalog/dependencies-query/function-info/source-functions';
import { WriteFunctions } from '../../queries/catalog/dependencies-query/function-info/write-functions';

export interface NetworkFunctionsConfig extends MergeableRecord {
/** The list of function names that should be marked in the given context if their arguments match. */
fns: readonly string[]
export interface NetworkFunctionsConfig extends FunctionsToDetectConfig {
/** only trigger if the function's read argument is linked to a value that matches this pattern */
onlyTriggerWithArgument?: RegExp | string
}
Expand Down
21 changes: 21 additions & 0 deletions test/functionality/linter/lint-deprecated-functions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe } from 'vitest';
import { withTreeSitter } from '../_helper/shell';
import { assertLinter, controlledPkgDb } from '../_helper/linter';
import { LintingResultCertainty } from '../../../src/linter/linter-format';
import { DeprecationState } from '../../../src/linter/rules/deprecated-functions';

describe('flowR linter', withTreeSitter(parser => {
describe('deprecated functions', () => {
Expand Down Expand Up @@ -65,5 +66,25 @@ dplyr::all_equal(first, second)`, 'deprecated-functions',
{ fns: ['recode'], noPkgDb: true }
);
});

describe('only detect deprecated args when present', () => {
assertLinter('deprecated arg but not present', parser, 'testFn()',
'deprecated-functions',
[],
{ totalCalls: 1, totalFunctionDefinitions: 1 },
{ fns: ['testFn'], whenArgs: { 'testFn': [{ argName: 'badArg', state: DeprecationState.Deprecated }] } }
);

assertLinter('deprecated arg present', parser, 'testFn(badArg=5)',
'deprecated-functions',
[{
certainty: LintingResultCertainty.Certain,
function: 'testFn',
loc: [1, 1, 1, 16]
}],
{ totalCalls: 1, totalFunctionDefinitions: 1 },
{ fns: ['testFn'], whenArgs: { 'testFn': [{ argName: 'badArg', state: DeprecationState.Deprecated }] } }
);
});
});
}));
Loading