Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A tutorial for stale feature flag cleanup #395

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
53 changes: 53 additions & 0 deletions demo/custom_cleanups.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"### Custom Cleanups \n",
"\n",
"This is a guide for developing automation for ***custom code cleanups***.\n",
"\n",
"Languages supported : Java, Kotlin, Go, Python, Swift, and TypeScript\n",
"\n",
"ℹ️ **_NOTE:_** This tutorial is in Python for pedagogical purposes. \n",
"Piranha can be used with the CLI or using the Rust API. \n",
"We would encourage following this tutorial even if you do not intend to use the Python API.\n",
"\n",
"✅ **Prerequisite**: [Stale FF cleanup tutorial](./stale_feature_flag_cleanup_tutorial.ipynb)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".env",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.10.9"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "8940432f2365cfc2130f5c62b266313bc73e38258967dc33b60edac5998749e5"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
82 changes: 0 additions & 82 deletions demo/stale_feature_flag_cleanup_demos.py

This file was deleted.

262 changes: 262 additions & 0 deletions demo/stale_feature_flag_cleanup_tutorial.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"### Cleaning up code related to stale feature flags\n",
"\n",
"This is a guide for developing automation for ***cleaning up code related to stale feature flags*** \n",
"\n",
"Languages supported : Java, Kotlin, Go and Swift\n",
"\n",
"\n",
"ℹ️ **_NOTE:_** This tutorial is in Python for pedagogical purposes. \n",
"Piranha can be used with the CLI or using the Rust API. \n",
"We would encourage following this tutorial even if you do not intend to use the Python API.\n",
"\n",
"Now let's set up the notebook by installing the polyglot-piranha from source. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Please install [rust](https://rustup.rs/)\n",
"! python3 -m venv ../.env\n",
"! cd ../ && cargo build --no-default-features && maturin develop\n",
"\n",
"from polyglot_piranha import PiranhaArguments, execute_piranha, RuleGraph, Rule, OutgoingEdges"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"tags": []
},
"source": [
"<details>\n",
" <summary><b>CLI</b></summary>\n",
" <p> <b>Build the executable from source</b> </p>\n",
" <code>\n",
" cargo build --no-default-features -r\n",
" </code>\n",
" <p> <i>The executable <code>polyglot_piranha</code> will be available under <code>target/release</code></i> </p>\n",
"</details>\n",
"\n",
"Now let's take the below example where we want to clean up the code related to the stale flag `SOME_STALE_FLAG` 🏴 because it has been permanently enabled."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"input_code = \"\"\"package com.uber.piranha;\n",
"class SampleJava {\n",
" \n",
" // An enum that declares the feature flag \n",
" enum FeatureFlag {\n",
" SOME_STALE_FLAG,\n",
" }\n",
"\n",
" public void sampleMethod(ExperimentInterface exp) {\n",
" if (exp.isToggleEnabled(SOME_STALE_FLAG)) {\n",
" System.out.println(\"SOME_STALE_FLAG is enabled\");\n",
" } else {\n",
" System.out.println(\"SOME_STALE_FLAG is disabled\");\n",
" }\n",
" }\n",
"}\n",
"\"\"\""
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"<details>\n",
" <summary><b>CLI</b></summary>\n",
" <p> <b>A similar example for CLI is available <a href=\"./feature_flag_cleanup/java/SampleClass.ja\"> here </a></b> </p>\n",
" <code>\n",
" cargo build --no-default-features -r\n",
" </code>\n",
" <p> <i>The executable <code>polyglot_piranha</code> will be available under <code>target/release</code></i> </p>\n",
"</details>\n",
"\n",
"\n",
"We will now craft a Piranha rule to update the feature flag API `exp.isToggleEnabled(SOME_STALE_FLAG)` and delete the enum case `SOME_STALE_FLAG`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# a rule to update `exp.isToggleEnabled(SOME_STALE_FLAG)`\n",
"update_rule_toggle_enabled = Rule (\n",
" name= \"update_toggle_enabled\",\n",
" query= \"\"\"((\n",
" (method_invocation \n",
" name : (_) @name\n",
" arguments: ((argument_list \n",
" ([\n",
" (field_access field: (_)@argument)\n",
" (_) @argument\n",
" ])) )\n",
" ) @method_invocation)\n",
" (#eq? @name \"isToggleEnabled\")\n",
" (#eq? @argument \"@stale_flag_name\")\n",
" )\"\"\",\n",
" replace_node=\"method_invocation\",\n",
" replace=\"@is_treated\",\n",
" holes= set([\"stale_flag_name\", \"is_treated\"]),\n",
" # Try commenting out the below line and see the difference in output\n",
" groups= set([\"replace_expression_with_boolean_literal\"])\n",
")\n",
"\n",
"# a rule to delete enum case `SOME_STALE_FLAG`\n",
"delete_enum_case = Rule (\n",
" name = \"delete_enum_constant\",\n",
" query = \"\"\"(\n",
" ((enum_constant name : (_) @enum_case) @ec) \n",
" (#eq? @enum_case \"@stale_flag_name\")\n",
" )\"\"\",\n",
" replace_node = \"ec\",\n",
" replace = \"\",\n",
" holes = set([\"stale_flag_name\"]),\n",
" # Try commenting out the below line and see the difference in output\n",
" groups = set([\"delete_enum_entry\"]),\n",
")\n",
"\n",
"rule_graph= RuleGraph(rules=[update_rule_toggle_enabled, delete_enum_case], edges=[])\n",
"\n",
"piranha_args = PiranhaArguments(\n",
" language = \"java\",\n",
" code_snippet= input_code,\n",
" rule_graph= rule_graph,\n",
" substitutions= {'stale_flag_name': 'SOME_STALE_FLAG', 'is_treated': 'true'},\n",
" dry_run= True,\n",
" cleanup_comments = True\n",
")\n",
"output = execute_piranha(piranha_args)\n",
"output_content= output[0].content\n",
"assert output_content != input_code # sanity test for this tutorial\n",
"print(output_content)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"The rule `update_rule_toggle_enabled` specifies a rule that matches against an expression like `exp.isTreated(SOME_STALE_FLAG)` and replaces it with `true`. \n",
" \n",
"<details>\n",
" <summary>See the rule</summary>\n",
"\n",
" ##### `update_rule_toggle_enabled`\n",
" ```python Rule (\n",
" name= \"update_toggle_enabled\",\n",
" query= \"\"\"((\n",
" (method_invocation \n",
" name : (_) @name\n",
" arguments: ((argument_list \n",
" ([\n",
" (field_access field: (_)@argument)\n",
" (_) @argument\n",
" ])) )\n",
" ) @method_invocation)\n",
" (#eq? @name \"isToggleEnabled\")\n",
" (#eq? @argument \"@stale_flag_name\")\n",
" )\"\"\",\n",
" replace_node=\"method_invocation\",\n",
" replace=\"@is_treated\",\n",
" holes= set([\"stale_flag_name\", \"is_treated\"])\n",
" # Try un-commenting the below line and see the difference in output\n",
" groups= set([\"replace_expression_with_boolean_literal\"])\n",
")\n",
" ```\n",
"</details>\n",
"\n",
"\n",
"The **`query`** property of the rule is a [tree-sitter query](https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries) that is matched against the source code to identify specific AST patterns.\n",
"\n",
"ℹ️ **_NOTE:_** Use [tree-sitter playground](https://tree-sitter.github.io/tree-sitter/playground) to craft the query that matches your specific APIs. This playground allows a user to create a query and match it against some input source code. This playground \"syntax highlights\" in the input code snippet based on input query. \n",
"<details>\n",
"<summary>Using the tree-sitter play ground locally </summary>\n",
"Install tree-sitter [CLI](https://github.com/tree-sitter/tree-sitter/blob/master/cli/README.md).\n",
"```bash\n",
"git clone https://github.com/tree-sitter/tree-sitter-java.git # Or any other tree-sitter language repo\n",
"cd tree-sitter-java\n",
"tree-sitter generate \n",
"tree-sitter build-wasm\n",
"tree-sitter playground\n",
"```\n",
"</details>\n",
"\n",
"The node captured by the tag-name specified in the **`replace_node`** property is replaced with the pattern specified in the **`replace`** property.\n",
"The **`replace`** pattern can use the tags from the **`query`** to construct a replacement based on the match (somewhat similar to [regex-replace](https://docs.microsoft.com/en-us/visualstudio/ide/using-regular-expressions-in-visual-studio?view=vs-2022)).\n",
"\n",
"Each rule also contains the **`groups`** property, that specifies the kind of change performed by this rule. Based on this group, appropriate\n",
"cleanup will be performed by Piranha. For instance, `replace_expression_with_boolean_literal` will trigger deep cleanups to eliminate dead code (like eliminating `consequent` of a `if statement`) caused by replacing an expression with a boolean literal.\n",
"Currently, Piranha provides deep clean-ups for edits that belong the groups - `replace_expression_with_boolean_literal`, `delete_enum_entry`, and `delete_method`. Adding an appropriate entry to the groups, hooks up the user defined rules to the pre-built cleanup rules.\n",
"\n",
"The property **`holes`** specify the holes or template variables `@treated` and `@stale_flag_name` that need to be replaced with some concrete values so that the rule matches only the feature flag API usages corresponding to a specific flag, and replace it specifically with `true` or `false`.\n",
"\n",
"The values for these template variables is specified via the `substitutions` dictionary. This dictionary is required to construct piranha arguments. \n",
"\n",
"<details>\n",
" <summary><b>CLI</b></summary>\n",
" <h4>Java</h4>\n",
" <code>\n",
" ./target/release/polyglot_piranha -c demo/feature_flag_cleanup/java -f demo/feature_flag_cleanup/java/configurations -l \"java\" -s stale_flag_name=SAMPLE_STALE_FLAG -s treated=true -s treated_complement=false\n",
" </code>\n",
" \n",
" <h4>Kotlin</h4>\n",
" <code>\n",
" ./target/release/polyglot_piranha -c demo/feature_flag_cleanup/kt -f demo/feature_flag_cleanup/kt/configurations -l \"kt\" -s stale_flag_name=SAMPLE_STALE_FLAG -s treated=true -s treated_complement=false\n",
" </code>\n",
"</details>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.9"
},
"vscode": {
"interpreter": {
"hash": "8940432f2365cfc2130f5c62b266313bc73e38258967dc33b60edac5998749e5"
}
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Loading