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

Script to add new rule file #123

Merged
merged 23 commits into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from 14 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ build/
docs/source/rules/
coverage.xml
coverage_report.html
.idea/
80 changes: 76 additions & 4 deletions docs/source/build_a_lint_rule.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,86 @@
" ..."
]
},
{
"cell_type": "raw",
"metadata": {},
acharles7 marked this conversation as resolved.
Show resolved Hide resolved
"source": [
"Iteration order of attributes is the same as the order they appear in the source code.\n",
"In this case, that means visit_If_test is called before visit_If_body and visit_If_orelse.\n",
"\n",
"Use fixit's cli to generate a skeleton of new rule file::\n",
"\n",
" $ python -m fixit.cli.add_new_rule # Creates new_rule.py at fixit/rules/new_rule.py\n",
" $ python -m fixit.cli.add_new_rule --path fixit/rules/my_rule.py # Creates rule file at path specified\n",
"\n",
"This will generate a model rule file used to create new rule."
]
},
{
"cell_type": "code",
"execution_count": null,
"outputs": [],
"source": [
"import libcst as cst\n",
"import libcst.matchers as m\n",
"\n",
"from fixit import CstLintRule, InvalidTestCase as Invalid, ValidTestCase as Valid\n",
"\n",
"class Rule(CstLintRule):\n",
" \"\"\"\n",
" docstring or new_rule description\n",
" \"\"\"\n",
"\n",
" MESSAGE = \"Enter rule description message\"\n",
"\n",
" VALID = [Valid(\"'example'\")] # Valid examples\n",
"\n",
" INVALID = [Invalid(\"'example'\")] # Invalid examples\n",
" ..."
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
}
},
{
"cell_type": "raw",
"source": [
"Use above structure to create a new rule. \n",
"\n",
"The Declarative Matcher API\n",
"===========================\n",
"\n",
"Once we have a ``ClassDef`` node, we need to see if it contains a base class named ``object``.\n",
"We could implement by inspecting attributes of the node using equality and isinstance."
],
"metadata": {
"collapsed": false
}
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"# check if any of the base classes of this class def is \"object\"\n",
"def visit_ClassDef(self, node: cst.ClassDef):\n",
" has_object_base = any(\n",
" isinstance(arg.value, cst.Name) and arg.value.value == \"object\"\n",
" for arg in node.bases\n",
" )"
]
},
acharles7 marked this conversation as resolved.
Show resolved Hide resolved
{
"cell_type": "raw",
"metadata": {
"raw_mimetype": "text/restructuredtext"
},
"source": [
"Iteration order of attributes is the same as the order they appear in the source code.\n",
"In this case, that means visit_If_test is called before visit_If_body and visit_If_orelse.\n",
"Use above structure to create a new rule. \n",
"\n",
"The Declarative Matcher API\n",
"===========================\n",
Expand Down Expand Up @@ -323,9 +395,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.5"
"version": "3.7.0b1"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
}
95 changes: 95 additions & 0 deletions fixit/cli/add_new_rule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

# Usage:
#
# $ python -m fixit.cli.add_new_rule --help
# $ python -m fixit.cli.add_new_rule
# $ python -m fixit.cli.add_new_rule --path fixit/rules/new_rule.py

import argparse
from pathlib import Path

from libcst.codemod._cli import invoke_formatter

from fixit.common.config import get_lint_config


_LICENCE = """# Copyright (c) Facebook, Inc. and its affiliates.
acharles7 marked this conversation as resolved.
Show resolved Hide resolved
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

"""

_IMPORTS = """
acharles7 marked this conversation as resolved.
Show resolved Hide resolved
import libcst as cst
import libcst.matchers as m

from fixit import CstLintRule, InvalidTestCase as Invalid, ValidTestCase as Valid

"""

_TO_DOS = """
\"""
This is a model rule file for adding a new rule to fixit module
\"""

"""

_RULE_CLASS = """
class Rule(CstLintRule):
\"""
docstring or new_rule description
\"""

MESSAGE = "Enter rule description message"

VALID = [Valid("'example'")]

INVALID = [Invalid("'example'")]
"""


def is_path_exists(path: str) -> Path:
"""Check for valid path, if yes, return `Path` else raise `Error` """
filepath = Path(path)
if filepath.exists():
raise FileNotFoundError(f"{filepath} already exists")
acharles7 marked this conversation as resolved.
Show resolved Hide resolved
elif not filepath.parent.exists():
raise TypeError(f"{filepath} is not a valid path, provide path with file name")
else:
return filepath


def create_rule_file(file_path: Path) -> None:
"""Create a new rule file."""
context = _LICENCE + _IMPORTS + _TO_DOS + _RULE_CLASS
acharles7 marked this conversation as resolved.
Show resolved Hide resolved
updated_context = invoke_formatter(get_lint_config().formatter, context)

with open(file_path, "w") as f:
f.write(updated_context)

print(f"Successfully created {file_path.name} rule file at {file_path.parent}")


def main() -> None:
parser = argparse.ArgumentParser(
description="Creates a skeleton of adding new rule file"
)
parser.add_argument(
"-p",
"--path",
type=is_path_exists,
default=Path("fixit/rules/new_rule.py"),
help="Path to add rule file, Default is fixit/rules/new_rule.py",
)

args = parser.parse_args()
create_rule_file(args.path)


if __name__ == "__main__":
main()