Skip to content

Commit

Permalink
Feature flag debt cleaner (#5630)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/488551667048375/1209355305106376

### Description
Toll to flean up staled feature flags


### Steps to test this PR

_Test_
- [ ] follow the steps in the [README](34ed3ed?short_path=0d4ef7f#diff-0d4ef7fd5e89570aa7a95aa2f6ce47d1719161b2ec8102a7daa7ca5716dffb16) file
- [ ] Verify you can remove a feature flag setting its final value to `true` or `false`
  • Loading branch information
aitorvs authored Feb 13, 2025
1 parent 767bfa2 commit ad7a563
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 0 deletions.
102 changes: 102 additions & 0 deletions scripts/piranha/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Kotlin Feature Flag Cleanup CLI

A command-line tool for cleaning up stale feature flags in Kotlin projects.

## Installation

Ensure you have **Python 3.12** installed. Clone the repository and install dependencies:

```sh
git clone https://github.com/duckduckgo/Android.git
cd <path/to>/Android/scripts/piranha
pip install -r requirements.txt
```

## Requirements

- Python 3.12
- Dependencies listed in `requirements.txt`

### Step 1: Install Python 3.12 for your platform
Windows:
- Download Python 3.12 (https://www.python.org/downloads/release/python-3120/) and install it

Linux:
```sh
sudo apt update
sudo apt install python3.12 python3.12-venv
```

MacOS:
```sh
brew install python@3.12
```

### Step 2: Create a Virtual Environment with Python 3.12
Windows:
```sh
py -3.12 -m venv myenv
```

MacOS/Linux:
```sh
python3.12 -m venv myenv
```

### Step 3: Activate the Virtual Environment
Windows:
```sh
myenv\Scripts\activate
```

MacOS/Linux
```sh
source myenv/bin/activate
```

### Step 4: Verify Python Version Inside Virtual Environment
```sh
python --version
```

## Usage

```sh
python clean.py [-h] -r ROOT_PATH -c CONFIGURATION_PATH -f FLAGS [FLAGS ...]
```

## Options

- `-h, --help`
Show this help message and exit.

- `-r ROOT_PATH, --root-path ROOT_PATH`
Root path for searching Kotlin files.

- `-c CONFIGURATION_PATH, --configuration-path CONFIGURATION_PATH`
Path for the configuration files.

- `-f FLAGS [FLAGS ...], --flags FLAGS [FLAGS ...]`
Space-separated list of flag configurations in the format:
`flagname:treated_value` (e.g., `'flag1:true flag2:false'`).

## Explanation

- **`configuration-path`**: Path to the `rules.toml` file containing cleanup rules.
- **`root-path`**: The root path of your Android project.

## Example Execution

Assuming you're in the Android project folder, you can run:

```sh
python scripts/piranha/clean.py -r . -c scripts/piranha/configurations/ -f screenLock:false optimizeTrackerEvaluationV2:true
```

This command runs the cleanup tool in the current directory (`.`), using the configuration files from `scripts/piranha/configurations/` and applying the flag transformations provided.

## Notes

- The script modifies Kotlin files based on the given flag configurations.
- Use carefully, as it may remove or alter code.

86 changes: 86 additions & 0 deletions scripts/piranha/clean.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import os
import argparse
import logging
from os.path import join, dirname
from polyglot_piranha import execute_piranha, PiranhaArguments

# Set up logging
FORMAT = "%(levelname)s %(name)s %(asctime)-15s %(filename)s:%(lineno)d %(message)s"
logging.basicConfig(format=FORMAT)
logging.getLogger().setLevel(logging.DEBUG)
logger = logging.getLogger(__name__)

def get_folders_with_kotlin_files(root_path):
"""Finds all folders containing Kotlin (.kt) files starting from the given root path."""
folders_with_kt_files = []

for root, _, files in os.walk(root_path):
if any(file.endswith('.kt') for file in files):
folders_with_kt_files.append(root)

return folders_with_kt_files

def run_ff_cleaner(flags_config, root_path, configuration_path):
"""Runs Piranha feature flag cleanup for Kotlin with the provided flag configurations and root path.
Args:
flags_config: List of tuples containing (flag_name, treated_value)
root_path: Path to the root directory
configuration_path: Path to configuration files
"""
logger.info("Running the stale feature flag cleanup demo for Kotlin")

for flag_name, treated in flags_config:
logger.info(f"Processing flag: {flag_name} with treated value: {treated}")
# Convert treated to boolean (in case it's passed as a string)
treated_bool = treated.lower() == "true"

# Compute treated_complement programmatically
treated_complement = "false" if treated_bool else "true"

args = PiranhaArguments(
"kt",
substitutions={
"treated": str(treated_bool).lower(),
"treated_complement": treated_complement,
"flag_name": flag_name,
},
allow_dirty_ast=True,
paths_to_codebase=get_folders_with_kotlin_files(root_path),
path_to_configurations=configuration_path,
cleanup_comments=True,
delete_consecutive_new_lines=True,
)

execute_piranha(args)
logger.info(f"Piranha execution completed for flag: {flag_name}")

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Run stale feature flag cleanup for Kotlin.")
parser.add_argument("-r", "--root-path", type=str,
default=os.environ.get('FF_ROOT_PATH'),
help="Root path for searching Kotlin files (default: $FF_ROOT_PATH)")
parser.add_argument("-c", "--configuration-path", type=str,
default=os.environ.get('FF_CLEAN_CONFIGURATION_PATH'),
help="Path for the configuration files (default: $FF_CLEAN_CONFIGURATION_PATH)")
parser.add_argument("-f", "--flags", type=str, required=True, nargs="+",
help="Space-separated list of flag configurations in format: flagname:treated_value " +
"(e.g., 'flag1:true flag2:false')")

args = parser.parse_args()

# Parse flag configurations
flags_config = []
for flag_arg in args.flags:
try:
flag_name, treated = flag_arg.split(":")
if treated.lower() not in ["true", "false"]:
raise ValueError(f"Invalid treated value for flag {flag_name}: {treated}")
flags_config.append((flag_name, treated))
except ValueError as e:
parser.error(f"Invalid flag configuration format: {flag_arg}. Use format 'flagname:treated_value'. {str(e)}")

run_ff_cleaner(flags_config, args.root_path, args.configuration_path)

print("Completed running the stale feature flag cleanup demo")

63 changes: 63 additions & 0 deletions scripts/piranha/configurations/rules.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
[[rules]]
name = "replace_isEnabled_with_boolean_literal"
query = """(
(call_expression
(navigation_expression
(call_expression
(navigation_expression (_) (navigation_suffix (simple_identifier) @feature_call))
(call_suffix (value_arguments))
)
(navigation_suffix (simple_identifier) @m_name)
)
(call_suffix)
) @call_expression
(#eq? @feature_call @flag_name)
(#eq? @m_name "isEnabled")
)"""
replace_node = "call_expression"
replace = "@treated"
groups = ["replace_expression_with_boolean_literal"]
holes = ["treated", "flag_name"]

[[rules]]
name = "delete_feature_flag"
query = """(
(class_declaration
(class_body
(function_declaration
(simple_identifier) @feature_call
(function_value_parameters)
(user_type
(type_identifier) @toggle_type
)
) @function_declaration
)
)
(#eq? @feature_call @flag_name)
(#eq? @toggle_type "Toggle")
)"""
replace_node = "function_declaration"
replace = ""
holes = ["flag_name"]
groups = ["delete_feature_flag"]

[[rules]]
name = "remove_calls_onto_feature_flag"
query = """(
(call_expression
(navigation_expression
(call_expression
(navigation_expression (_) (navigation_suffix (simple_identifier) @feature_call))
(call_suffix (value_arguments))
)
(navigation_suffix (simple_identifier))
)
(call_suffix)
) @call_expression
(#eq? @feature_call @flag_name)
)"""
replace_node = "call_expression"
replace = ""
groups = ["remove_calls_onto_feature_flag"]
holes = ["flag_name"]

1 change: 1 addition & 0 deletions scripts/piranha/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
polyglot_piranha==0.3.29

0 comments on commit ad7a563

Please sign in to comment.