├── .devcontainer └── devcontainer.json ├── .github ├── dependabot.yml └── workflows │ ├── dependency-review.yml │ ├── gritql.yaml │ └── main.yaml ├── .gitignore ├── .grit ├── .gitignore ├── grit.yaml ├── patterns │ ├── css │ │ └── aspect_ratio.md │ ├── go │ │ ├── channel_guarded_with_mutex.md │ │ ├── cloudflare_go_v2.md │ │ ├── common.grit │ │ ├── exported_loop_pointer.md │ │ ├── go_importing.md │ │ ├── go_imports.grit │ │ ├── hidden_goroutine.md │ │ ├── jwt_go_none_algorithm.md │ │ ├── mux_go_v5.md │ │ ├── no_strconv_atoi.md │ │ ├── no_unnecessary_conditionals.md │ │ ├── path_to_filepath.md │ │ └── useless_if_else_body.md │ ├── grit_snake_case.md │ ├── java │ │ ├── correct_false_comparison_operator.md │ │ ├── correct_true_comparison_operator.md │ │ ├── java.grit │ │ ├── junit_ignored_tests.md │ │ ├── no_big_decimal_double.md │ │ ├── no_self_assigned_variables.md │ │ ├── no_unthrown_exceptions.md │ │ ├── rewrite_simple_functions.md │ │ ├── unnecessary_equality_comparison.md │ │ ├── unused_private_fields.md │ │ └── unused_private_methods.md │ ├── js │ │ ├── _convert_default_exports.md │ │ ├── _convert_default_imports.md │ │ ├── _no_restricted_imports.md │ │ ├── _test_move_import.md │ │ ├── _test_scope_shadow.md │ │ ├── add_playwright_tags.md │ │ ├── add_try_catch_async_functions.md │ │ ├── add_type_caught_errors.md │ │ ├── angularjs_to_angular.md │ │ ├── calling_set_state_on_current_state.md │ │ ├── chai_to_jest.md │ │ ├── codecept_to_playwright.md │ │ ├── common.grit │ │ ├── convert_PureComponent_to_Component.md │ │ ├── convert_fragment_to_react_fragment.md │ │ ├── convert_negative_zero_equality_check_to_object_is.md │ │ ├── curly.md │ │ ├── cypress_to_playwright.md │ │ ├── drizzle_mysql_postgresql.md │ │ ├── enforce_strict_equality_check.md │ │ ├── enzyme_rtl.md │ │ ├── es6_arrow_function_braces.md │ │ ├── es6_arrow_functions.md │ │ ├── es6_exports.md │ │ ├── es6_imports.md │ │ ├── es6_imports_to_require.md │ │ ├── explicit_type_conversion.md │ │ ├── find_uncaught_http_request.md │ │ ├── fix_jsx_file_extension.md │ │ ├── for_direction.md │ │ ├── graphql_v3_csrf_prevention.md │ │ ├── graphql_v4_csrf_prevention.md │ │ ├── hathora_ts.md │ │ ├── importing.md │ │ ├── imports.grit │ │ ├── index_of_to_includes.md │ │ ├── inner_html_to_inner_text.md │ │ ├── intelligent_useEffect_to_useLayoutEffect.md │ │ ├── iots_to_zod.md │ │ ├── jest_array_containing.md │ │ ├── jest_no_skipped_tests.md │ │ ├── jest_to_vitest.md │ │ ├── knockout_to_react.md │ │ ├── langfuse_node_v2.md │ │ ├── marzano.grit │ │ ├── migrate_default_imports.md │ │ ├── migrating_from_react_query_3_to_react_query_4.md │ │ ├── moment-to-date-fns.grit │ │ ├── moment_to_datefns.md │ │ ├── move_defined_styled_components_outside_module_level.md │ │ ├── mui4_to_mui5.md │ │ ├── mux_v8.md │ │ ├── next13_links.md │ │ ├── no_alert.md │ │ ├── no_anonymous_default_export.md │ │ ├── no_array_constructor.md │ │ ├── no_async_promise_executor.md │ │ ├── no_bitwise.md │ │ ├── no_caller.md │ │ ├── no_commented_out_code.md │ │ ├── no_confirm.md │ │ ├── no_console_log.md │ │ ├── no_dead_code.md │ │ ├── no_debugger.md │ │ ├── no_eq_null.md │ │ ├── no_inline_if.md │ │ ├── no_iterator.md │ │ ├── no_new_object.md │ │ ├── no_new_symbol.md │ │ ├── no_prompt.md │ │ ├── no_prototype_builtins.md │ │ ├── no_restricted.grit │ │ ├── no_return_assign.md │ │ ├── no_throw_literal.md │ │ ├── no_unsafe_negation.md │ │ ├── no_yoda_conditions.md │ │ ├── openai_v4.md │ │ ├── openrouter.md │ │ ├── prefer_early_return.md │ │ ├── prefer_is_nan.md │ │ ├── prefer_self_closing_tag_jsx.md │ │ ├── proto_to_class.md │ │ ├── protractor_to_playwright.md │ │ ├── react.grit │ │ ├── react_hooks.grit │ │ ├── react_named_imports.md │ │ ├── react_to_hooks.md │ │ ├── react_to_hooks_mobx.md │ │ ├── remove_apollo_graphql_schema_directives_for_v3_v4.md │ │ ├── remove_escape_markup.md │ │ ├── remove_node_buffer_offset_check_flag.md │ │ ├── remove_should_component_update_from_pure_components.md │ │ ├── remove_unsafe_params_from_serialize_javascript.md │ │ ├── replaceAll_to_replace.md │ │ ├── replace_wildcard_imports.md │ │ ├── replace_zlib_deflate_to_zlib_deflateSync.md │ │ ├── serverless_to_spin.md │ │ ├── shadow_scope.grit │ │ ├── split_trpc_router.md │ │ ├── todo.grit │ │ ├── unused_imports.md │ │ ├── use_exponentiation_operator.md │ │ ├── useless_ternary_operator.md │ │ ├── util_literal.md │ │ ├── util_upsert.md │ │ ├── variable_scoping.md │ │ ├── warning_for_hardcoded_github_token.md │ │ └── wrap_playwright_locators.md │ ├── json │ │ ├── auto_upgrade.md │ │ ├── dependency.grit │ │ ├── public_s3_bucket.md │ │ ├── reverse_json_kv.md │ │ ├── strict_tsconfig.md │ │ └── wildcard_assume_role.md │ ├── markdown │ │ └── delinkify.md │ ├── python │ │ ├── _accesses.md │ │ ├── _py_ai_rewrite.md │ │ ├── _test_add_multiple_bare_imports.md │ │ ├── _test_add_one_bare_import.md │ │ ├── _test_conditional_imports.md │ │ ├── _test_correct_source.md │ │ ├── _test_ensure_import_from.md │ │ ├── _test_is_bare_imported.md │ │ ├── _test_python_imports.md │ │ ├── _test_replace_import.md │ │ ├── _test_replace_import_list.md │ │ ├── _test_two_imports.md │ │ ├── airflow_decorator_syntax.md │ │ ├── any_to_in.md │ │ ├── aware_utc.md │ │ ├── binary_op_ident.md │ │ ├── bool_in_expr_ident.md │ │ ├── cleaned_data_vs_post.md │ │ ├── collection_builtin.md │ │ ├── collection_to_bool.md │ │ ├── combined_bound_check.md │ │ ├── comp_to_generator.md │ │ ├── del_comprehension.md │ │ ├── dict_comprehension.md │ │ ├── dict_get_instead_of_if_else_block.md │ │ ├── in_dict_keys.md │ │ ├── insecure_hash_function.md │ │ ├── instructor_to_openai.md │ │ ├── join_nested_withs.md │ │ ├── json_response_vs_json.md │ │ ├── jwt_python_none_algorithm.md │ │ ├── langfuse_v2.md │ │ ├── math_prod.md │ │ ├── mux_python_v3.md │ │ ├── no_null_string_field.md │ │ ├── no_skipped_tests.md │ │ ├── open_file_object_never_closed.md │ │ ├── openai.md │ │ ├── openai_azure.md │ │ ├── openai_global.md │ │ ├── print_to_log.md │ │ ├── py_importing.md │ │ ├── py_imports.grit │ │ ├── py_marzano.grit │ │ ├── py_no_debugger.md │ │ ├── py_todo.grit │ │ ├── re_match_walrus.md │ │ ├── replace_tempfile.md │ │ ├── sql_alchemy_v2.md │ │ ├── tempfile_without_flush.md │ │ ├── ternary_op.md │ │ ├── thousands_separator.md │ │ ├── trulens_eval_migration.md │ │ ├── use_defusedcsv.md │ │ ├── use_flask_jsonify.md │ │ └── warning_for_hardcoded_tmp_path.md │ ├── rust │ │ ├── _test_todo.md │ │ ├── byte_count_to_len.md │ │ ├── cargo_use_long_dependency.md │ │ ├── collapsible_if.md │ │ ├── collapsible_match.md │ │ ├── no_needless_return.md │ │ ├── no_useless_format.md │ │ ├── rust_todo.grit │ │ └── use_secure_hashes.md │ ├── solidity │ │ ├── EtherTransfer.md │ │ ├── NestedLoop.md │ │ ├── NoMulDivRoundUp.md │ │ ├── NoUnusedVariables.md │ │ └── UpgradableProxyPattern.md │ ├── sql │ │ ├── add_pg_unit_test.md │ │ ├── oracle.grit │ │ ├── oracle_nextval_to_pg.wip │ │ ├── oracle_quote_procedure.md │ │ └── oracle_to_pg.md │ ├── terraform │ │ ├── edit_module.md │ │ └── kv_pair.md │ ├── universal │ │ ├── ai │ │ │ ├── _ai_generate.md │ │ │ ├── _ai_is_bare.md │ │ │ ├── _ai_match.md │ │ │ ├── _ai_rewrite.md │ │ │ ├── _ai_transform.md │ │ │ ├── _ai_transform_simple.md │ │ │ ├── _js_transform.md │ │ │ ├── ai.grit │ │ │ ├── ai_choose_example.md │ │ │ └── ai_rewrite.grit │ │ ├── basic_example.grit │ │ ├── blocks.grit │ │ ├── files.grit │ │ ├── lists.grit │ │ ├── replacements.grit │ │ ├── sort_fn.md │ │ ├── test.grit │ │ └── use_blocks.md │ └── yaml │ │ ├── actions_runner.md │ │ ├── concourse_v7.md │ │ └── yaml.grit └── workflows │ └── styled.ts ├── .prettierrc ├── CONTRIBUTING.md ├── README.md ├── old_patterns ├── CheckZero.md ├── NonTrivialMath.md ├── ReentrancyBeforeAndAfter.md ├── ReentrancyLowRisk.md ├── ReentrancyNoBefore.md ├── shared.grit └── system.grit ├── ops ├── fix_tags.py ├── install.sh └── set_access_token.sh ├── samples └── styled.in.tsx └── wip ├── AddTypeScriptTypes.md ├── ConvertPropTypes.md ├── FlowToTypeScript.md ├── RemoveUnusedImports.md ├── StyledJSXToCSSModules.md ├── builtin_shadow.md ├── grit_distribute_or.md └── grit_example_labels.md /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Default Linux Universal", 3 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 4 | "image": "mcr.microsoft.com/devcontainers/universal:2-linux", 5 | "features": { 6 | }, 7 | 8 | "onCreateCommand": "bash ./ops/install.sh" 9 | } 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "devcontainers" 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, 4 | # surfacing known-vulnerable versions of the packages declared or updated in the PR. 5 | # Once installed, if the workflow run is marked as required, 6 | # PRs introducing known-vulnerable packages will be blocked from merging. 7 | # 8 | # Source repository: https://github.com/actions/dependency-review-action 9 | name: 'Dependency Review' 10 | on: [pull_request] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | dependency-review: 17 | runs-on: nscloud-ubuntu-22.04-amd64-4x16 18 | steps: 19 | - name: 'Checkout Repository' 20 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 21 | - name: 'Dependency Review' 22 | uses: actions/dependency-review-action@0efb1d1d84fc9633afcdaad14c485cbbc90ef46c # v2.5.1 23 | -------------------------------------------------------------------------------- /.github/workflows/gritql.yaml: -------------------------------------------------------------------------------- 1 | name: grit-check 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | run: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Check out code 16 | uses: actions/checkout@v4 17 | - name: grit-check 18 | uses: getgrit/github-action-check@v0 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /.grit/.gitignore: -------------------------------------------------------------------------------- 1 | .gritmodules 2 | *.log 3 | -------------------------------------------------------------------------------- /.grit/grit.yaml: -------------------------------------------------------------------------------- 1 | version: 0.0.1 2 | patterns: [] 3 | -------------------------------------------------------------------------------- /.grit/patterns/css/aspect_ratio.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Aspect ratio 3 | --- 4 | 5 | ```grit 6 | language css 7 | 8 | `a { $props }` where { $props <: contains `aspect-ratio: $x` } 9 | ``` 10 | 11 | ## Matches the right selector and declaration block 12 | 13 | ```css 14 | a { 15 | width: calc(100% - 80px); 16 | aspect-ratio: 1/2; 17 | font-size: calc(10px + (56 - 10) * ((100vw - 320px) / (1920 - 320))); 18 | } 19 | 20 | #some-id { 21 | some-property: 5px; 22 | } 23 | 24 | a.b ~ c.d { 25 | } 26 | .e.f + .g.h { 27 | } 28 | 29 | @font-face { 30 | font-family: 'Open Sans'; 31 | src: url('/a') format('woff2'), url('/b/c') format('woff'); 32 | } 33 | ``` 34 | 35 | ```css 36 | a { 37 | width: calc(100% - 80px); 38 | aspect-ratio: 1/2; 39 | font-size: calc(10px + (56 - 10) * ((100vw - 320px) / (1920 - 320))); 40 | } 41 | 42 | #some-id { 43 | some-property: 5px; 44 | } 45 | 46 | a.b ~ c.d { 47 | } 48 | .e.f + .g.h { 49 | } 50 | 51 | @font-face { 52 | font-family: 'Open Sans'; 53 | src: url('/a') format('woff2'), url('/b/c') format('woff'); 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /.grit/patterns/go/common.grit: -------------------------------------------------------------------------------- 1 | language go 2 | 3 | // All core stdlib functions can be done here 4 | private pattern before_each_file_stdlib() { before_each_file_prep_imports() } 5 | 6 | private pattern after_each_file_stdlib() { after_each_file_handle_imports() } 7 | 8 | pattern before_each_file() { before_each_file_stdlib() } 9 | 10 | pattern after_each_file() { after_each_file_stdlib() } 11 | -------------------------------------------------------------------------------- /.grit/patterns/go/go_imports.grit: -------------------------------------------------------------------------------- 1 | language go 2 | 3 | pattern before_each_file_prep_imports() { 4 | $_ where { 5 | // Array of global imports. Each import is a tuple of (source, as) 6 | $GLOBAL_NEW_IMPORTS = [] 7 | } 8 | } 9 | 10 | // Handle inserting imports if we accumulated any 11 | pattern after_each_file_handle_imports() { 12 | $_ where { 13 | if ($GLOBAL_NEW_IMPORTS <: not []) { 14 | $program <: or { 15 | contains import_declaration(imports=$list) as $outer where { 16 | $list <: or { 17 | import_spec_list(imports=$imports) where { 18 | $GLOBAL_NEW_IMPORTS <: some bubble($imports) $import where { 19 | $imports += `"$import"` 20 | } 21 | }, 22 | import_spec() where { 23 | $GLOBAL_NEW_IMPORTS <: some bubble($outer) $import where { 24 | $outer += `\nimport "$import"` 25 | } 26 | } 27 | } 28 | }, 29 | contains package_clause() as $anchor where { 30 | $GLOBAL_NEW_IMPORTS <: some bubble($anchor) $import where { 31 | $anchor += `\nimport "$import"` 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | function require_import($source, $as) { 40 | // Default condition 41 | $split_source = split($source, "/"), 42 | if ($as <: undefined) { $name_to_use = $split_source[-1] } else { 43 | $name_to_use = $as 44 | }, 45 | or { 46 | $program <: contains bubble($source, $name_to_use) import_spec($name, path=$path) where { 47 | $path <: contains $source, 48 | if ($name <: not .) { $name_to_use = $name } 49 | }, 50 | // $GLOBAL_NEW_IMPORTS <: some bubble($source) `$import` where $import <: $source, 51 | $GLOBAL_NEW_IMPORTS += $source 52 | }, 53 | return $name_to_use 54 | } 55 | 56 | pattern import_of($source) { 57 | import_declaration(imports=import_spec_list(imports=$mod)) where { 58 | $mod <: not contains $source, 59 | $mod += $source 60 | } 61 | } 62 | 63 | pattern ensure_import($source) { 64 | import_declaration(imports=import_spec_list(imports=$mod)) where { 65 | $mod <: not contains $source, 66 | $mod += $source 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.grit/patterns/go/no_strconv_atoi.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Replace strconv.Atoi ⇒ strconv.ParseInt 3 | tags: [fix, correctness] 4 | --- 5 | 6 | Identified a potential risk in converting the outcome of a `strconv.Atoi` command to int16. This may lead to integer overflow, possibly causing unforeseen issues and even privilege escalation. It is recommended to utilize `strconv.ParseInt` instead. 7 | 8 | ### references 9 | 10 | - [strconv](https://pkg.go.dev/strconv) 11 | 12 | 13 | ```grit 14 | language go 15 | 16 | `strconv.Atoi($inputStr)` => `strconv.ParseInt($inputStr, 10, 16)` 17 | ``` 18 | 19 | ## Replace strconv.Atoi ⇒ strconv.ParseInt 20 | 21 | ```go 22 | package main 23 | 24 | import ( 25 | "fmt" 26 | "strconv" 27 | ) 28 | 29 | func mainInt16Ex1() { 30 | bigValue, err := strconv.Atoi("2147483648") 31 | if err != nil { 32 | panic(err) 33 | } 34 | value := int16(bigValue) 35 | fmt.Println(value) 36 | } 37 | 38 | func mainInt32Ex1() { 39 | bigValue, err := strconv.Atoi("2147483648") 40 | if err != nil { 41 | panic(err) 42 | } 43 | value := int32(bigValue) 44 | fmt.Println(value) 45 | } 46 | 47 | func main() { 48 | mainInt16Ex1() 49 | mainInt32Ex1() 50 | } 51 | ``` 52 | 53 | ```go 54 | package main 55 | 56 | import ( 57 | "fmt" 58 | "strconv" 59 | ) 60 | 61 | func mainInt16Ex1() { 62 | bigValue, err := strconv.ParseInt("2147483648", 10, 16) 63 | if err != nil { 64 | panic(err) 65 | } 66 | value := int16(bigValue) 67 | fmt.Println(value) 68 | } 69 | 70 | func mainInt32Ex1() { 71 | bigValue, err := strconv.ParseInt("2147483648", 10, 16) 72 | if err != nil { 73 | panic(err) 74 | } 75 | value := int32(bigValue) 76 | fmt.Println(value) 77 | } 78 | 79 | func main() { 80 | mainInt16Ex1() 81 | mainInt32Ex1() 82 | } 83 | ``` 84 | -------------------------------------------------------------------------------- /.grit/patterns/go/no_unnecessary_conditionals.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Avoid unnecessary if statements 3 | tags: [fix, warning] 4 | --- 5 | 6 | If statements that always evaluate to `true` or `false` are redundant and should be removed. 7 | 8 | 9 | ```grit 10 | language go 11 | 12 | or { 13 | `if(false){ $body }` => ., 14 | `if(true){ $body }` => $body 15 | } 16 | ``` 17 | 18 | ## Warning for INCORRECT comparison `if (True)` 19 | 20 | ```go 21 | package main 22 | 23 | import "fmt" 24 | 25 | func mainFunc() { 26 | fmt.Println("hello world") 27 | var y = "hello"; 28 | 29 | if (true) { 30 | fmt.Println("never") 31 | } 32 | } 33 | ``` 34 | 35 | ```go 36 | package main 37 | 38 | import "fmt" 39 | 40 | func mainFunc() { 41 | fmt.Println("hello world") 42 | var y = "hello"; 43 | 44 | fmt.Println("never") 45 | } 46 | ``` 47 | 48 | 49 | ## Warning for INCORRECT comparison `if (False)` 50 | 51 | ```go 52 | package main 53 | 54 | import "fmt" 55 | 56 | func mainFunc() { 57 | fmt.Println("hello world") 58 | var y = "hello"; 59 | 60 | if (false) { 61 | fmt.Println("never") 62 | } 63 | } 64 | ``` 65 | 66 | 67 | ```go 68 | package main 69 | 70 | import "fmt" 71 | 72 | func mainFunc() { 73 | fmt.Println("hello world") 74 | var y = "hello"; 75 | 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /.grit/patterns/go/path_to_filepath.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Replace `path.Join()` ⇒ `filepath.Join()` 3 | tags: [fix, correctness] 4 | --- 5 | 6 | Utilize `filepath.Join(...)` instead of `path.Join(...)` as it accommodates OS-specific path separators, mitigating potential issues on systems like Windows that may employ different delimiters. 7 | 8 | ### references 9 | 10 | - [path.join-considered-harmful](https://parsiya.net/blog/2019-03-09-path.join-considered-harmful/) 11 | - [path.go](https://go.dev/src/path/path.go?s=4034:4066#L145) 12 | 13 | 14 | ```grit 15 | language go 16 | 17 | or { 18 | `path.Join($params)` => `filepath.Join($params)`, 19 | ensure_import(`"path/filepath"`) 20 | } 21 | ``` 22 | 23 | ## Replace `path.Join()` ⇒ `filepath.Join()` 24 | 25 | ```go 26 | package main 27 | 28 | import ( 29 | "fmt" 30 | "path" 31 | ) 32 | 33 | func getDirectory() string { 34 | return "/some/directory" // Replace this with your logic to get the directory 35 | } 36 | 37 | func exampleFunction1() { 38 | dir := getDirectory() 39 | 40 | var joinedPath = path.Join(getDirectory()) 41 | 42 | var filePath = filepath.Join(getDirectory()) 43 | 44 | path.Join("/", path.Base(joinedPath)) 45 | } 46 | 47 | func exampleFunction2() { 48 | fmt.Println(path.Join(url.Path, "baz")) 49 | } 50 | 51 | func exampleFunction3(p string) { 52 | fmt.Println(path.Join(p, "baz")) 53 | 54 | fmt.Println(path.Join("asdf", "baz")) 55 | 56 | fmt.Println(filepath.Join(a.Path, "baz")) 57 | } 58 | 59 | ``` 60 | 61 | ```go 62 | package main 63 | 64 | import ( 65 | "fmt" 66 | "path" 67 | "path/filepath" 68 | ) 69 | 70 | func getDirectory() string { 71 | return "/some/directory" // Replace this with your logic to get the directory 72 | } 73 | 74 | func exampleFunction1() { 75 | dir := getDirectory() 76 | 77 | var joinedPath = filepath.Join(getDirectory()) 78 | 79 | var filePath = filepath.Join(getDirectory()) 80 | 81 | filepath.Join("/", path.Base(joinedPath)) 82 | } 83 | 84 | func exampleFunction2() { 85 | fmt.Println(filepath.Join(url.Path, "baz")) 86 | } 87 | 88 | func exampleFunction3(p string) { 89 | fmt.Println(filepath.Join(p, "baz")) 90 | 91 | fmt.Println(filepath.Join("asdf", "baz")) 92 | 93 | fmt.Println(filepath.Join(a.Path, "baz")) 94 | } 95 | 96 | ``` 97 | -------------------------------------------------------------------------------- /.grit/patterns/go/useless_if_else_body.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Identical statements in the if else body 3 | tags: [fix, correctness] 4 | --- 5 | 6 | Identical statements found in both the `if` and `else` bodies of an `if-statement`. This results in the same code execution regardless of the if-expression outcome. To optimize, eliminate the `if` statement entirely. 7 | 8 | ```grit 9 | language go 10 | 11 | or { 12 | `if ($conditon) { $body } else { $body }`, 13 | `if ($conditon) { $body } else if ($conditon) { $body }` 14 | } => `if ($conditon) { 15 | $body 16 | }` 17 | ``` 18 | 19 | ## Detected identical statements in the else if 20 | 21 | ```go 22 | package main 23 | 24 | import "fmt" 25 | 26 | func main() { 27 | var y = 1 28 | 29 | if (y) { 30 | fmt.Println("of course") 31 | } 32 | 33 | // useless-if-conditional 34 | if (y) { 35 | fmt.Println("same condition") 36 | } else if (y) { 37 | fmt.Println("same condition") 38 | } 39 | 40 | } 41 | ``` 42 | 43 | ```go 44 | package main 45 | 46 | import "fmt" 47 | 48 | func main() { 49 | var y = 1 50 | 51 | if (y) { 52 | fmt.Println("of course") 53 | } 54 | 55 | // useless-if-conditional 56 | if (y) { 57 | fmt.Println("same condition") 58 | } 59 | 60 | } 61 | ``` 62 | 63 | ## Detected identical statements in the if else 64 | 65 | ```go 66 | package main 67 | 68 | import "fmt" 69 | 70 | func main() { 71 | var y = 1 72 | 73 | // useless-if-body 74 | if (y) { 75 | fmt.Println("of course") 76 | } else { 77 | fmt.Println("of course") 78 | } 79 | } 80 | ``` 81 | 82 | ```go 83 | package main 84 | 85 | import "fmt" 86 | 87 | func main() { 88 | var y = 1 89 | 90 | // useless-if-body 91 | if (y) { 92 | fmt.Println("of course") 93 | } 94 | } 95 | ``` 96 | 97 | ## Detected identical statements in the different if else 98 | 99 | ```go 100 | package main 101 | 102 | import "fmt" 103 | 104 | func main() { 105 | var y = 1 106 | 107 | if (y) { 108 | fmt.Println("of course") 109 | } else { 110 | fmt.Println("different condition") 111 | } 112 | } 113 | ``` 114 | -------------------------------------------------------------------------------- /.grit/patterns/grit_snake_case.md: -------------------------------------------------------------------------------- 1 | # Use snake case for pattern names 2 | 3 | Markdown files committed to stdlib's patterns directory must have names conforming to GritQL's pattern name convention. 4 | 5 | ```grit 6 | engine marzano(0.1) 7 | language markdown 8 | 9 | file($name, $body) where { 10 | $name <: r".*?/?([^/]+)\.[a-zA-Z]*"($base_name), 11 | ! $base_name <: r"^[a-zA-Z_][a-zA-Z0-9_]*$" 12 | } 13 | ``` 14 | 15 | ## Examples 16 | 17 | ### Invalid 18 | 19 | ```md 20 | 21 | 22 | # This is a markdown file 23 | ``` 24 | 25 | Still bad: 26 | 27 | ```md 28 | 29 | 30 | # This is a markdown file 31 | ``` 32 | 33 | ### Valid 34 | 35 | ```md 36 | 37 | 38 | # This is a markdown file 39 | ``` 40 | -------------------------------------------------------------------------------- /.grit/patterns/java/correct_false_comparison_operator.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Correct comparison operator `$x = false` 3 | tags: [good-practice] 4 | --- 5 | 6 | Assignment inside a condition like this `$x = false` is usually accidental, this is likely meant to be a comparison `$x == false`. 7 | 8 | ```grit 9 | language java 10 | 11 | `$var = false` => `$var == false` where { 12 | $var <: within `if ($cond) { $body }` , 13 | $var <: within $cond 14 | } 15 | ``` 16 | 17 | ## $x = true 18 | 19 | ```java 20 | class Bar { 21 | void main() { 22 | boolean myBoolean; 23 | if (myBoolean = false) { 24 | continue; 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | ```java 31 | class Bar { 32 | void main() { 33 | boolean myBoolean; 34 | if (myBoolean == false) { 35 | continue; 36 | } 37 | } 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /.grit/patterns/java/correct_true_comparison_operator.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Correct comparison operator `$x = true` 3 | tags: [good-practice] 4 | --- 5 | 6 | Assignment inside a condition is usually accidental, this is likely meant to be a comparison. 7 | 8 | ```grit 9 | language java 10 | 11 | `$var = true` => `$var == true` where { 12 | $var <: within `if ($cond) { $body }` , 13 | $var <: within $cond 14 | } 15 | ``` 16 | 17 | ## $x = true 18 | 19 | ```java 20 | class Bar { 21 | void main() { 22 | boolean myBoolean; 23 | if (myBoolean = true) { 24 | continue; 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | ```java 31 | class Bar { 32 | void main() { 33 | boolean myBoolean; 34 | if (myBoolean == true) { 35 | continue; 36 | } 37 | } 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /.grit/patterns/java/java.grit: -------------------------------------------------------------------------------- 1 | language java 2 | 3 | // All core stdlib functions can be done here 4 | private pattern before_each_file_stdlib() { file() } 5 | 6 | private pattern after_each_file_stdlib() { after_each_file_global_rewrites() } 7 | 8 | // These could be redefined in the future (not presently supported) 9 | pattern before_each_file() { before_each_file_stdlib() } 10 | 11 | pattern after_each_file() { after_each_file_stdlib() } 12 | -------------------------------------------------------------------------------- /.grit/patterns/java/no_big_decimal_double.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "`BigDecimal(double)` should not be used" 3 | tags: [java] 4 | --- 5 | 6 | Because of floating point imprecision, the `BigDecimal(double)` constructor can be somewhat unpredictable. It is better to use `BigDecimal.valueOf(double)`. 7 | 8 | 9 | ```grit 10 | language java 11 | 12 | `new BigDecimal($x)` => `BigDecimal.valueOf($x)` where or { 13 | $program <: contains variable_declarator($name, $value) where { 14 | $name <: `$x`, 15 | $value <: decimal_floating_point_literal() 16 | }, 17 | $x <: decimal_floating_point_literal() 18 | } 19 | ``` 20 | 21 | ## Transforms BigDecimal constructor with double argument 22 | 23 | ```java 24 | double d = 1.1; 25 | 26 | BigDecimal bd1 = new BigDecimal(d); 27 | BigDecimal bd2 = new BigDecimal(1.1); 28 | ``` 29 | 30 | ```java 31 | double d = 1.1; 32 | 33 | BigDecimal bd1 = BigDecimal.valueOf(d); 34 | BigDecimal bd2 = BigDecimal.valueOf(1.1); 35 | ``` 36 | -------------------------------------------------------------------------------- /.grit/patterns/java/no_self_assigned_variables.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Variables should not be self-assigned 3 | tags: [java] 4 | --- 5 | 6 | # Variables should not be self-assigned 7 | 8 | It is redundant and usually a bug when a variable is assigned to itself. 9 | 10 | 11 | ```grit 12 | language java 13 | 14 | assignment_expression($left, $right) as $assignment where { 15 | $left <: `$right`, 16 | or { 17 | and { $left <: not contains `this`, $left => `this.$left` }, 18 | $assignment <: within expression_statement() as $exp where { $exp => . } 19 | } 20 | } 21 | ``` 22 | 23 | ## Optimistically rewrites variable to instance variable when it is not one 24 | 25 | ```java 26 | class Watermelon { 27 | private String name; 28 | 29 | public void setName(String name) { 30 | name = name; 31 | } 32 | } 33 | ``` 34 | 35 | ```java 36 | class Watermelon { 37 | private String name; 38 | 39 | public void setName(String name) { 40 | this.name = name; 41 | } 42 | } 43 | ``` 44 | 45 | ## Removes self-assigned instance variables 46 | 47 | ```java 48 | class Watermelon { 49 | public void setName(String name) { 50 | this.name = this.name; 51 | void method = oranges.map((n) -> { this.orange = this.orange; }); 52 | } 53 | } 54 | ``` 55 | 56 | ```java 57 | class Watermelon { 58 | public void setName(String name) { 59 | void method = oranges.map((n) -> { }); 60 | } 61 | } 62 | ``` 63 | -------------------------------------------------------------------------------- /.grit/patterns/java/no_unthrown_exceptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Exceptions should not be created without being thrown 3 | tags: [java] 4 | --- 5 | 6 | # Exceptions should not be created without being thrown 7 | 8 | Creating a new Throwable without actually throwing or binding it is useless and is probably due to a mistake. 9 | 10 | 11 | ```grit 12 | language java 13 | 14 | object_creation_expression($type) as $x where { 15 | $type <: or { 16 | r".*Exception", 17 | r".*Error" 18 | }, 19 | $x <: not within throw_statement() , 20 | $x <: not within variable_declarator($value) where { $value <: $x } , 21 | $x => `throw $x` 22 | } 23 | ``` 24 | 25 | ## Throws unthrown exception 26 | 27 | ```java 28 | class BadClass { 29 | public String doSomething(int x) { 30 | if (x < 0) { 31 | new IllegalArgumentException("x must be nonnegative"); 32 | } 33 | if (x > 100) { 34 | throw new IllegalArgumentException("x must be less than 100"); 35 | } 36 | IllegalArgumentException saveIt = new IllegalArgumentException("Don't correct this"); 37 | new NotAThrowable(); 38 | return "some-string"; 39 | } 40 | } 41 | ``` 42 | 43 | ```java 44 | class BadClass { 45 | public String doSomething(int x) { 46 | if (x < 0) { 47 | throw new IllegalArgumentException("x must be nonnegative"); 48 | } 49 | if (x > 100) { 50 | throw new IllegalArgumentException("x must be less than 100"); 51 | } 52 | IllegalArgumentException saveIt = new IllegalArgumentException("Don't correct this"); 53 | new NotAThrowable(); 54 | return "some-string"; 55 | } 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /.grit/patterns/java/rewrite_simple_functions.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: [ai, java, inline, quality] 3 | --- 4 | 5 | # Inline methods that are only used once 6 | 7 | This pattern uses static analysis to find private methods that are only used once, then uses AI to inline them. 8 | 9 | ```grit 10 | language java 11 | 12 | class_body($declarations) where { 13 | $declarations <: contains bubble($declarations) { 14 | method_declaration($name, $modifiers) as $method where { 15 | $modifiers <: contains `private`, 16 | $modifiers <: not contains or { 17 | marker_annotation(), 18 | `native` 19 | }, 20 | $name <: not or { 21 | `writeObject`, 22 | `readObject` 23 | }, 24 | $method => ai_rewrite($method, "Inline the use of the private method $name in the class."), 25 | $uses = 0, 26 | $declarations <: contains bubble($uses, $name, $method) `$name` where { 27 | $name <: not within $method , 28 | $uses += 1 29 | }, 30 | $uses <: 1 31 | } 32 | } 33 | } 34 | ``` 35 | 36 | ## Java Class 37 | 38 | This Java class has a private method that is only used in one location. We should inline it. 39 | 40 | ```java 41 | public class Calculator { 42 | public int add(int a, int b) { 43 | return a + b; 44 | } 45 | 46 | public int multiply(int a, int b) { 47 | return multiplyHelper(a, b); 48 | } 49 | 50 | private int multiplyHelper(int x, int y) { 51 | return x * y; 52 | } 53 | } 54 | 55 | ``` 56 | 57 | With the rewrite applied, Grit will output: 58 | 59 | ```java 60 | public class Calculator { 61 | public int add(int a, int b) { 62 | return a + b; 63 | } 64 | 65 | public int multiply(int a, int b) { 66 | return a * b; 67 | } 68 | } 69 | ``` 70 | 71 | ## Ignore reuse 72 | 73 | If a method is used multiple times, it should not be inlined. 74 | 75 | ```java 76 | public class Calculator { 77 | public String add(int a, int b) { 78 | return humanize(a + b); 79 | } 80 | 81 | public String multiply(int a, int b) { 82 | return humanize(a * b); 83 | } 84 | 85 | private String humanize(int result) { 86 | return result.toString(); 87 | } 88 | } 89 | ``` 90 | -------------------------------------------------------------------------------- /.grit/patterns/java/unnecessary_equality_comparison.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Unnecessary equality `==` Comparison 3 | tags: [good-practice] 4 | --- 5 | 6 | Simplify redundant self-comparison `($var == $var)` to achieve clearer code logic and avoid unnecessary repetition. 7 | 8 | ```grit 9 | language java 10 | 11 | `if($var == $var) { $body }` => `$body` 12 | ``` 13 | 14 | ## $x == #x 15 | 16 | ```java 17 | class Bar { 18 | void main() { 19 | boolean myBoolean; 20 | if(myBoolean == myBoolean){ 21 | continue; 22 | } 23 | } 24 | } 25 | ``` 26 | 27 | ```java 28 | class Bar { 29 | void main() { 30 | boolean myBoolean; 31 | continue; 32 | } 33 | } 34 | ``` 35 | 36 | ## $x == #y 37 | 38 | ```java 39 | class Bar { 40 | void main() { 41 | boolean myBoolean; 42 | boolean myBoolean2; 43 | if(myBoolean == myBoolea2){ 44 | continue; 45 | } 46 | } 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /.grit/patterns/java/unused_private_methods.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Unused private methods should be removed 3 | tags: [java] 4 | --- 5 | 6 | # Unused private methods should be removed 7 | 8 | Unused private methods, excepting methods with annotations and special methods overriding Java's default behaviour, constitute dead code and should therefore be removed. 9 | 10 | 11 | ```grit 12 | language java 13 | 14 | class_body($declarations) where { 15 | $declarations <: contains bubble($declarations) { 16 | method_declaration(name=$unused_method, $modifiers) as $unused_decl where { 17 | $modifiers <: contains `private`, 18 | $modifiers <: not contains or { 19 | marker_annotation(), 20 | `native` 21 | }, 22 | $unused_method <: not or { 23 | `writeObject`, 24 | `readObject` 25 | }, 26 | $declarations <: not contains $unused_method until method_declaration($name) where { 27 | $name <: `$unused_method` 28 | }, 29 | $unused_decl => . 30 | } 31 | } 32 | } 33 | ``` 34 | 35 | ## Removes unused non-constructor method 36 | 37 | ```java 38 | public class Foo implements Serializable 39 | { 40 | private Foo(){} 41 | public static void doSomething(){ 42 | Foo foo = new Foo(); 43 | } 44 | 45 | public void sayHi() { 46 | this.usedPrivateMethod(); 47 | } 48 | 49 | private void usedPrivateMethod() { } 50 | 51 | private void unusedPrivateMethod() { } 52 | 53 | private void anotherUnusedPrivateMethod() { } 54 | } 55 | ``` 56 | 57 | ```java 58 | public class Foo implements Serializable 59 | { 60 | private Foo(){} 61 | public static void doSomething(){ 62 | Foo foo = new Foo(); 63 | } 64 | 65 | public void sayHi() { 66 | this.usedPrivateMethod(); 67 | } 68 | 69 | private void usedPrivateMethod() { } 70 | 71 | 72 | } 73 | ``` 74 | 75 | ## Does not remove annotated and override methods 76 | 77 | ```java 78 | public class Foo implements Serializable 79 | { 80 | @Annotation 81 | private void annotatedMethod(){ 82 | this.usedPrivateMethod(); 83 | } 84 | private void writeObject(ObjectOutputStream s){ } 85 | private void readObject(ObjectInputStream in){ } 86 | } 87 | ``` 88 | -------------------------------------------------------------------------------- /.grit/patterns/js/_no_restricted_imports.md: -------------------------------------------------------------------------------- 1 | # No Restricted Imports 2 | 3 | This pattern provides the equivalent of the `no-restricted-imports` [rule in ESLint](https://eslint.org/docs/latest/rules/no-restricted-imports). 4 | 5 | You _must_ provide a pattern that will match the imports you want to restrict. 6 | 7 | ```grit 8 | language js 9 | 10 | // Example use to prevent importing from the `lodash` package or anything from inside `@shared/internal/` 11 | no_restricted_imports($modules = or { 12 | "lodash", 13 | r"@shared/internal/(.*)"($_) 14 | }) 15 | ``` 16 | 17 | ## Lodash example 18 | 19 | ```js 20 | import { get } from 'lodash'; 21 | ``` 22 | 23 | ```js 24 | import { get } from 'lodash'; 25 | ``` 26 | 27 | ## Internal example 28 | 29 | ```js 30 | import { get } from '@shared/internal/whatever'; 31 | ``` 32 | 33 | ```js 34 | import { get } from '@shared/internal/whatever'; 35 | ``` 36 | 37 | ## Clean example 38 | 39 | ```js 40 | import { get } from '@shared/whatever'; 41 | ``` 42 | 43 | ```js 44 | import { get } from '@shared/whatever'; 45 | ``` 46 | -------------------------------------------------------------------------------- /.grit/patterns/js/add_playwright_tags.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Add tags to Playwright test descriptions 3 | tags: [hidden] 4 | --- 5 | 6 | Add tags to Playwright test descriptions. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | function add_prefixes($description) js { 14 | const words = $description.text.split(' '); 15 | const firstWith = words.findIndex((w) => w.startsWith('@')); 16 | for (let x = firstWith + 1; x < words.length; x++) { 17 | if (!words[x].startsWith('@') && (words[x] !== "Search" || words[x - 1] !== "@Advanced")) { 18 | words[x] = '@' + words[x] 19 | } 20 | } 21 | return words.join(' '); 22 | } 23 | 24 | `test($description, $_)` where { $description => add_prefixes($description) } 25 | ``` 26 | 27 | ## Adds tags properly 28 | 29 | ```js 30 | test('A nice test @1 ABCDE Account Studio @Projects Patterns', async ({ 31 | page, 32 | factory, 33 | context, 34 | }) => { 35 | expect(true).toBe(true); 36 | }); 37 | ``` 38 | 39 | ```js 40 | test('A nice test @1 @ABCDE @Account @Studio @Projects @Patterns', async ({ 41 | page, 42 | factory, 43 | context, 44 | }) => { 45 | expect(true).toBe(true); 46 | }); 47 | ``` 48 | -------------------------------------------------------------------------------- /.grit/patterns/js/add_try_catch_async_functions.md: -------------------------------------------------------------------------------- 1 | # Add try catch for async functions 2 | 3 | ```grit 4 | engine marzano(0.1) 5 | language js 6 | 7 | `async ($args) => { $body }` where { 8 | $body <: not contains `try`, 9 | $body => ` try { 10 | $body 11 | } catch (e) { 12 | console.log(e); 13 | }` 14 | } 15 | ``` 16 | 17 | ## Wraps async call with try and catch 18 | 19 | ```ts 20 | const testFunc = async () => { 21 | const response = await fetchApiInformation(); 22 | }; 23 | ``` 24 | 25 | ```ts 26 | const testFunc = async () => { 27 | try { 28 | const response = await fetchApiInformation(); 29 | } catch (e) { 30 | console.log(e); 31 | } 32 | }; 33 | ``` 34 | -------------------------------------------------------------------------------- /.grit/patterns/js/add_type_caught_errors.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Add Type To Caught Errors 3 | tags: [js, ts] 4 | --- 5 | 6 | # Add Type To Caught Errors 7 | 8 | Add `any` type annotation to caught errors. It is a common source of tsc errors. 9 | 10 | ```grit 11 | engine marzano(1.0) 12 | language js 13 | 14 | catch_clause($parameter, $type) where { 15 | $type <: ., 16 | $parameter => `$parameter: any` 17 | } 18 | ``` 19 | 20 | ## Basic example 21 | 22 | ```ts 23 | function foo() { 24 | try { 25 | console.log('tada'); 26 | } catch (e) { 27 | console.log('oops'); 28 | } 29 | } 30 | 31 | try { 32 | console.log('tada'); 33 | } catch (e: Foo) { 34 | console.log('oops'); 35 | } 36 | ``` 37 | 38 | ```ts 39 | function foo() { 40 | try { 41 | console.log('tada'); 42 | } catch (e: any) { 43 | console.log('oops'); 44 | } 45 | } 46 | 47 | try { 48 | console.log('tada'); 49 | } catch (e: Foo) { 50 | console.log('oops'); 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /.grit/patterns/js/angularjs_to_angular.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: [angularjs, angular, upgrade, wip, hidden, ai, flaky] 3 | --- 4 | 5 | # Upgrade from AngularJS to Angular 6 | 7 | [WIP] This pattern provides a basic build configuration for upgrading from AngularJS to Angular. It is still a work in progress. 8 | 9 | ```grit 10 | language js 11 | 12 | `angular.module("$mod").component($name, $args)` as $old where { 13 | $capitalized = capitalize($mod), 14 | $componentName = `$[capitalized]Component`, 15 | $new = ai_transform($args, instruct="Convert this into an Angular component", pattern=contains $componentName) 16 | } => $new 17 | ``` 18 | 19 | ## PhoneCat - Component 20 | 21 | From [Angular PhoneCat](https://github.com/angular/angular-phonecat), following [this tutorial](https://angular.io/guide/upgrade#upgrading-components). 22 | 23 | ```js 24 | 'use strict'; 25 | 26 | angular.module('phoneList').component('phoneList', { 27 | templateUrl: 'phone-list/phone-list.template.html', 28 | controller: [ 29 | 'Phone', 30 | function PhoneListController(Phone) { 31 | this.phones = Phone.query(); 32 | this.orderProp = 'brand'; 33 | }, 34 | ], 35 | }); 36 | ``` 37 | 38 | This is the converted component: 39 | 40 | ```ts 41 | 'use strict'; 42 | 43 | import { Component } from '@angular/core'; 44 | import { Phone } from './phone.service'; 45 | 46 | @Component({ 47 | selector: 'app-phone-list', 48 | templateUrl: './phone-list.component.html', 49 | providers: [Phone], 50 | }) 51 | export class PhoneListComponent { 52 | phones: any[]; 53 | orderProp = 'brand'; 54 | 55 | constructor(private phoneService: Phone) { 56 | this.phones = this.phoneService.query(); 57 | } 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /.grit/patterns/js/calling_set_state_on_current_state.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Calling setState on the current state is always a no-op. so change the state like $Y(!$X) instead. 3 | tags: [fix] 4 | --- 5 | 6 | Calling setState on the current state is always a no-op. Did you mean to change the state like $Y(!$X) instead? 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | react_functional_component($props, $body) where { 14 | or { 15 | $body <: contains `const [$x, $y] = useState($boolean)`, 16 | $body <: contains `const [$x, $y] = React.useState($boolean)` 17 | }, 18 | $body <: contains `$y($x)` => `$y(!$x)` 19 | } 20 | ``` 21 | 22 | ## on useState `$y($x)` => `$y(!$x)` 23 | 24 | ```javascript 25 | const DilogBox = ({ 26 | params 27 | }) => { 28 | const [isOpen, setIsOpen] = useState(false); 29 | const [columnRef, setColumnRef] = useState(null); 30 | return ( 31 | 32 | 40 | 47 | 48 | ); 49 | }; 50 | ``` 51 | 52 | ```javascript 53 | const DilogBox = ({ 54 | params 55 | }) => { 56 | const [isOpen, setIsOpen] = useState(false); 57 | const [columnRef, setColumnRef] = useState(null); 58 | return ( 59 | 60 | 68 | 75 | 76 | ); 77 | }; 78 | ``` 79 | -------------------------------------------------------------------------------- /.grit/patterns/js/common.grit: -------------------------------------------------------------------------------- 1 | engine marzano(0.1) 2 | language js 3 | 4 | // Matches any statement that should end in a semi-colon to avoid possible ambiguity. 5 | // For example, in the following snippet: 6 | // ``` 7 | // let a = foo() 8 | // (x => print(x))("hello") 9 | // ``` 10 | // The second "statement" is actually a call expression that is chained to the `foo` call: 11 | // ``` 12 | // let a = foo()(x => print(x))("hello") 13 | // ``` 14 | // Hence, the first statement should end in a semi colon to avoid ambiguity. 15 | pattern should_follow_semi() { 16 | `$exp` where { 17 | $exp <: after or { 18 | expression_statement(), 19 | lexical_declaration() 20 | } 21 | } 22 | } 23 | 24 | // Common alias for var, let, and const declarations. 25 | pattern variable($declarations) { 26 | or { 27 | variable_declaration($declarations), 28 | lexical_declaration($declarations) 29 | } 30 | } 31 | 32 | pattern base_string() { 33 | or { 34 | string(), 35 | template_string() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.grit/patterns/js/convert_PureComponent_to_Component.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rewrite `PureComponent` ⇒ `Component` 3 | tags: [SD, React] 4 | --- 5 | 6 | If a `PureComponent` has the `shouldComponentUpdate` method, convert it to a regular `Component`. 7 | 8 | `React.PureComponent` provides an implementation for `shouldComponentUpdate()` which compares props by reference to determine if they have changed. 9 | If you overwrite it with your own implementation, it doesn't make sense to extend `React.PureComponent`. 10 | 11 | 12 | ```grit 13 | engine marzano(1.0) 14 | language js 15 | 16 | class_declaration($heritage, $body) where { 17 | $heritage <: contains `PureComponent` => `Component`, 18 | $body <: contains `shouldComponentUpdate` 19 | } 20 | ``` 21 | 22 | ## Convert to React.Component when extending React.PureComponent 23 | 24 | ```javascript 25 | class Foo extends React.PureComponent { 26 | customMethod() {} 27 | 28 | shouldComponentUpdate() {} 29 | 30 | render() { 31 | return ; 32 | } 33 | } 34 | 35 | class Foo extends React.Component { 36 | customMethod() {} 37 | 38 | shouldComponentUpdate() {} 39 | 40 | render() { 41 | return ; 42 | } 43 | } 44 | ``` 45 | 46 | ``` 47 | class Foo extends React.Component { 48 | customMethod() {} 49 | 50 | shouldComponentUpdate() {} 51 | 52 | render() { 53 | return ; 54 | } 55 | } 56 | 57 | class Foo extends React.Component { 58 | customMethod() {} 59 | 60 | shouldComponentUpdate() {} 61 | 62 | render() { 63 | return ; 64 | } 65 | } 66 | ``` 67 | 68 | ## Convert to Component when extending destructured PureComponent 69 | 70 | ```javascript 71 | class Foo extends PureComponent { 72 | customMethod() {} 73 | 74 | shouldComponentUpdate() {} 75 | 76 | render() { 77 | return ; 78 | } 79 | } 80 | ``` 81 | 82 | ```typescript 83 | class Foo extends Component { 84 | customMethod() {} 85 | 86 | shouldComponentUpdate() {} 87 | 88 | render() { 89 | return ; 90 | } 91 | } 92 | ``` 93 | -------------------------------------------------------------------------------- /.grit/patterns/js/convert_fragment_to_react_fragment.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Convert `<>` ⇒ `React.Fragment` 3 | tags: [fix] 4 | --- 5 | 6 | React suggest to use `React.Fragment` besides `<>` 7 | 8 | ```grit 9 | engine marzano(0.1) 10 | language js 11 | 12 | `<$name>$body` => `$body` where { 13 | $name <: false 14 | } 15 | ``` 16 | 17 | ## `<>` ⇒ `React.Fragment` 18 | 19 | ```javascript 20 | const Cat = (props) => { 21 | return ( 22 | <> 23 |

{props.name}

24 | 25 |
26 |

{props.color}

27 | <>{props.day} 28 |
29 | 30 | ); 31 | }; 32 | ``` 33 | 34 | ```javascript 35 | const Cat = (props) => { 36 | return ( 37 | 38 |

{props.name}

39 | 40 |
41 |

{props.color}

42 | {props.day} 43 |
44 |
45 | ); 46 | }; 47 | ``` 48 | -------------------------------------------------------------------------------- /.grit/patterns/js/convert_negative_zero_equality_check_to_object_is.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rewrite `x == -0` ⇒ `Object.is(x, -0)` 3 | tags: [SD] 4 | --- 5 | 6 | Convert any equality check with `-0` to the more precise `Object.is`. 7 | 8 | Details on [on StackOverflow](https://stackoverflow.com/questions/7223359/are-0-and-0-the-same). 9 | 10 | 11 | ```grit 12 | engine marzano(1.0) 13 | language js 14 | 15 | binary_expression($left, $operator, $right) as $exp where { 16 | $operator <: or { 17 | or { 18 | "==", 19 | "===" 20 | } where $exp => `Object.is($left, -0)`, 21 | or { 22 | "!=", 23 | "!==" 24 | } where $exp => `!Object.is($left, -0)` 25 | } 26 | } 27 | ``` 28 | 29 | ## Basic example 30 | 31 | ```javascript 32 | if (x == -0 || x !== -0) { 33 | foo(); 34 | } 35 | ``` 36 | 37 | ```typescript 38 | if (Object.is(x, -0) || !Object.is(x, -0)) { 39 | foo(); 40 | } 41 | ``` 42 | 43 | ## Converts an if condition 44 | 45 | ```javascript 46 | if (x == -0) { 47 | foo(); 48 | } else { 49 | foo(); 50 | } 51 | ``` 52 | 53 | ```typescript 54 | if (Object.is(x, -0)) { 55 | foo(); 56 | } else { 57 | foo(); 58 | } 59 | ``` 60 | 61 | ## Converts a while condition 62 | 63 | ```javascript 64 | while (x == -0) { 65 | foo(); 66 | } 67 | ``` 68 | 69 | ```typescript 70 | while (Object.is(x, -0)) { 71 | foo(); 72 | } 73 | ``` 74 | 75 | ## Converts a for condition 76 | 77 | ```javascript 78 | for (let x = 6; x != -0; x--) { 79 | foo(); 80 | } 81 | ``` 82 | 83 | ```typescript 84 | for (let x = 6; !Object.is(x, -0); x--) { 85 | foo(); 86 | } 87 | ``` 88 | 89 | ## Converts complex conditions 90 | 91 | ```javascript 92 | if (x == -0 && y != -0) { 93 | foo(); 94 | } 95 | ``` 96 | 97 | ```typescript 98 | if (Object.is(x, -0) && !Object.is(y, -0)) { 99 | foo(); 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /.grit/patterns/js/curly.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Enforces braces for if/for/do/while statements. 3 | tags: [good-practice] 4 | --- 5 | This matches the [eslint rule](https://eslint.org/docs/latest/rules/curly). 6 | 7 | 8 | ```grit 9 | engine marzano(1.0) 10 | language js 11 | 12 | or { 13 | if_statement(consequence=$body), 14 | for_statement($body), 15 | while_statement($body), 16 | do_statement($body), 17 | else_clause(else = $body) where { $body <: ! if_statement() } 18 | } where { $body <: not statement_block(), $body => `{$body}` } 19 | ``` 20 | 21 | Code examples: 22 | ## if 23 | ```js 24 | if (x > 0) 25 | doStuff(); 26 | ``` 27 | will become 28 | ```js 29 | if (x > 0) { 30 | doStuff(); 31 | } 32 | ``` 33 | 34 | ## else 35 | ```js 36 | if (x > 0) 37 | doStuff(); 38 | else 39 | console.log("e"); 40 | ``` 41 | will become 42 | ```js 43 | if (x > 0) { 44 | doStuff(); 45 | } else { 46 | console.log("e"); 47 | } 48 | ``` 49 | 50 | ## for 51 | ```js 52 | for (var i = 0; i < 10; i++) 53 | doStuff(); 54 | ``` 55 | will become 56 | ```js 57 | for (var i = 0; i < 10; i++) { 58 | doStuff(); 59 | } 60 | ``` 61 | ## while 62 | ```js 63 | while (x > 0) 64 | doStuff(); 65 | ``` 66 | will become 67 | ```js 68 | while (x > 0) { 69 | doStuff(); 70 | } 71 | ``` 72 | ## do 73 | ```js 74 | do 75 | doStuff(); 76 | while (x > 0); 77 | ``` 78 | will become 79 | ```js 80 | do { 81 | doStuff(); 82 | } while (x > 0); 83 | ``` 84 | -------------------------------------------------------------------------------- /.grit/patterns/js/enforce_strict_equality_check.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Non-strict `==` ⇒ strict `===` 3 | tags: [fix SD] 4 | --- 5 | 6 | Convert non-strict equality checking, using `==`, to the strict version, using `===`. 7 | 8 | Details on [StackOverflow](https://stackoverflow.com/questions/359494/which-equals-operator-vs-should-be-used-in-javascript-comparisons). 9 | 10 | ```grit 11 | engine marzano(0.1) 12 | language js 13 | 14 | or { 15 | `$x == $y` => `$x === $y`, 16 | `$x != $y` => `$x !== $y` 17 | } where { $y <: not `null` } 18 | ``` 19 | 20 | ## Rewrite == to === 21 | 22 | ```javascript 23 | if (x == 42) { 24 | foo; 25 | } 26 | ``` 27 | 28 | ```typescript 29 | if (x === 42) { 30 | foo; 31 | } 32 | ``` 33 | 34 | ## Rewrite != to !== 35 | 36 | ```javascript 37 | if (x != 42) { 38 | foo; 39 | } 40 | ``` 41 | 42 | ```typescript 43 | if (x !== 42) { 44 | foo; 45 | } 46 | ``` 47 | 48 | ## Doesn't apply when not necessary 49 | 50 | ```javascript 51 | if (foo) { 52 | foo; 53 | } 54 | ``` 55 | 56 | ## Doesn't apply when comparing with null 57 | 58 | ```javascript 59 | if (foo == null) { 60 | foo; 61 | } 62 | ``` 63 | -------------------------------------------------------------------------------- /.grit/patterns/js/es6_arrow_function_braces.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Always braces around arrow function body 3 | tags: [js, es6, migration] 4 | --- 5 | 6 | Converts arrow function single expression to to block body 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | or { 14 | `$params => ($obj)` => `($params) => { 15 | return $obj 16 | }`, 17 | `$params => $exp` => `($params) => { 18 | return $exp 19 | }` where $exp <: not contains or { 20 | return_statement(), 21 | statement_block() 22 | } 23 | } 24 | ``` 25 | 26 | ## Transform function expressions 27 | 28 | ```ts 29 | const shortSum = (a: number, b: number) => { 30 | return a + b; 31 | }; 32 | 33 | const add2 = (a: number) => { 34 | return a + 2; 35 | }; 36 | 37 | const getCode = () => { 38 | return { code: 'CC' }; 39 | }; 40 | 41 | const sum = (a: number, b: number) => { 42 | return a + b; 43 | }; 44 | 45 | const getPerson = () => { 46 | return { 47 | name: 'John', 48 | age: 30, 49 | }; 50 | }; 51 | 52 | const sum = (a: number, b: number) => { 53 | console.log(); 54 | }; 55 | 56 | const log = () => { 57 | console.log('Hello'); 58 | console.log('World'); 59 | }; 60 | 61 | const log2 = (arr) => { 62 | return console.log(arr); 63 | }; 64 | ``` 65 | -------------------------------------------------------------------------------- /.grit/patterns/js/es6_imports_to_require.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Prefer require over imports 3 | tags: [js, es6, cjs, commonjs] 4 | --- 5 | 6 | # ES6 imports to require 7 | 8 | Converts ES6-style `import` to `require` statements. 9 | 10 | ```grit 11 | engine marzano(0.1) 12 | language js 13 | 14 | or { 15 | `import { $import } from "$source"` where { 16 | $newports = [], 17 | $import <: some bubble($newports) { 18 | import_specifier($name, $alias) where or { 19 | and { $alias <: false, $newports += `$name` }, 20 | $newports += `$name: $alias` 21 | } 22 | }, 23 | $transformed = join(list=$newports, separator=", ") 24 | } => `const { $transformed } = require("$source")`, 25 | `import $import from "$source"` => `const $import = require("$source")` 26 | } 27 | ``` 28 | 29 | ## Transform standard require statements 30 | 31 | ```ts 32 | import { something, another } from './lib'; 33 | import { assert } from 'chai'; 34 | import { config as conf } from 'chai'; 35 | import { mixed as mixie, foo } from 'life'; 36 | import starImport from 'star'; 37 | 38 | // no special handling for default. Also, comments get removed. 39 | import defaultImport from '../../shared/default'; 40 | ``` 41 | 42 | ```ts 43 | const { something, another } = require("./lib") 44 | const { assert } = require("chai") 45 | const { config: conf } = require("chai") 46 | const { mixed: mixie, foo } = require("life") 47 | const starImport = require("star") 48 | 49 | // no special handling for default. Also, comments get removed. 50 | const defaultImport = require("../../shared/default") 51 | ``` 52 | -------------------------------------------------------------------------------- /.grit/patterns/js/explicit_type_conversion.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ⇒ explicit conversion between types 3 | tags: [SE] 4 | --- 5 | 6 | Use explicit conversions between types, e.g., `'' + x` => `String(s)`. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | or { 14 | or { 15 | `+$value`, 16 | `1 * $value` 17 | } => `Number($value)`, 18 | or { 19 | `"" + $value`, 20 | `$value + ""`, 21 | `'' + $value`, 22 | `$value + ''` 23 | } => `String($value)`, 24 | or { `!!$value` } => `Boolean($value)` 25 | } 26 | ``` 27 | 28 | ``` 29 | 30 | ``` 31 | 32 | ## Handles string preceding variable 33 | 34 | ```javascript 35 | var x = '' + foo; 36 | ``` 37 | 38 | ```typescript 39 | var x = String(foo); 40 | ``` 41 | 42 | ## Handles string following variable 43 | 44 | ```javascript 45 | var x = a + ''; 46 | ``` 47 | 48 | ```typescript 49 | var x = String(a); 50 | ``` 51 | 52 | ## Handles interpolated string 53 | 54 | ```javascript 55 | var x = foo + '' + bar; 56 | ``` 57 | 58 | ```typescript 59 | var x = String(foo) + bar; 60 | ``` 61 | 62 | ## Handles number conversion using + 63 | 64 | ```javascript 65 | var x = +foo; 66 | ``` 67 | 68 | ```typescript 69 | var x = Number(foo); 70 | ``` 71 | 72 | ## Handles number conversion using \* 73 | 74 | ```javascript 75 | var x = 1 * foo; 76 | ``` 77 | 78 | ```typescript 79 | var x = Number(foo); 80 | ``` 81 | 82 | ## Handles boolean conversion using !! 83 | 84 | ```javascript 85 | var x = !!"123"; 86 | ``` 87 | 88 | ```typescript 89 | var x = Boolean("123"); 90 | ``` 91 | -------------------------------------------------------------------------------- /.grit/patterns/js/find_uncaught_http_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ⇒ Find uncaught HTTP requests 3 | tags: [fix] 4 | --- 5 | 6 | Find uncaught HTTP requests and wrap it with try {} catch{ } 7 | 8 | ```grit 9 | engine marzano(0.1) 10 | language js 11 | 12 | or { 13 | `await request($body)` as $httpRequest => `try { $httpRequest } catch() {}` where { 14 | ! $httpRequest <: within `try { $_ } catch($catch) { $_ }` 15 | }, 16 | `return request($body)` as $httpRequestUnderFunc => `try { $httpRequestUnderFunc } catch(err) { return err}` where { 17 | ! $httpRequestUnderFunc <: within `async function $name(){ $httpRequestUnderFunc }` 18 | } 19 | } 20 | ``` 21 | 22 | ## Request without try catch block 23 | 24 | ```javascript 25 | await request('/bar'); 26 | ``` 27 | 28 | ```javascript 29 | try { await request('/bar') } catch() {} 30 | ``` 31 | 32 | ## Request with try catch block 33 | 34 | ```javascript 35 | try { 36 | await request('/foo'); 37 | } catch {} 38 | ``` 39 | 40 | ## Request with async function 41 | 42 | ```javascript 43 | async function doRequest() { 44 | return request("/bar"); 45 | } 46 | 47 | export function main() { 48 | try { 49 | await doRequest(); 50 | } catch { } 51 | } 52 | ``` 53 | 54 | ## Request without async function 55 | 56 | ```javascript 57 | function doRequest() { 58 | return request('/bar'); 59 | } 60 | ``` 61 | 62 | ```javascript 63 | function doRequest() { 64 | try { 65 | return request('/bar'); 66 | } catch (err) { 67 | return err; 68 | } 69 | } 70 | ``` 71 | -------------------------------------------------------------------------------- /.grit/patterns/js/fix_jsx_file_extension.md: -------------------------------------------------------------------------------- 1 | # Insert a .jsx extension on files that contain JSX 2 | 3 | Files containing JSX should have a .jsx extension. 4 | 5 | ```grit 6 | engine marzano(0.1) 7 | language js 8 | 9 | file($name, $body) where { 10 | $body <: contains or { 11 | jsx_element(), 12 | jsx_self_closing_element() 13 | }, 14 | $name <: or { 15 | r"(.+).js"($base) => `$base.jsx`, 16 | r"(.+).ts"($base) => `$base.tsx` 17 | } 18 | } 19 | ``` 20 | 21 | ## Handles a basic JSX element 22 | 23 | ```js 24 | export default function SomeReact() { 25 | return

This is JSX.

; 26 | } 27 | ``` 28 | 29 | ## Handles a self-closing JSX element 30 | 31 | ```js 32 | export default () => ; 33 | ``` 34 | -------------------------------------------------------------------------------- /.grit/patterns/js/for_direction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fix `for` counter direction 3 | tags: [bug, fix, good] 4 | --- 5 | 6 | If a `for` counter moves in the wrong direction the loop will run infinitely. Mostly, an infinite `for` loop is a typo and causes a bug. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | for_statement($condition, $increment, $initializer) where { 14 | or { 15 | and { 16 | $condition <: contains or { 17 | `$x < $_`, 18 | `$x <= $_`, 19 | `$_ > $x`, 20 | `$_ >= $x` 21 | }, 22 | $increment <: contains { `$x--` => `$x++` } 23 | }, 24 | and { 25 | $condition <: contains or { 26 | `$x > $_`, 27 | `$x >= $_`, 28 | `$_ < $x`, 29 | `$_ <= $x` 30 | }, 31 | $increment <: contains { `$x++` => `$x--` } 32 | } 33 | } 34 | } 35 | ``` 36 | 37 | ## Transform `for` counter for `<`/`<=` directions 38 | 39 | ```javascript 40 | for (var i = 0; i < 10; i--) { 41 | doSomething(i); 42 | } 43 | ``` 44 | 45 | ```typescript 46 | for (var i = 0; i < 10; i++) { 47 | doSomething(i); 48 | } 49 | ``` 50 | 51 | ## Transform `for` counter for `>`/`>=` directions 52 | 53 | ```javascript 54 | for (var i = 10; i >= 0; i++) { 55 | doSomething(i); 56 | } 57 | ``` 58 | 59 | ```typescript 60 | for (var i = 10; i >= 0; i--) { 61 | doSomething(i); 62 | } 63 | ``` 64 | 65 | ## Transform counter for `<`/`<=` directions 66 | 67 | ```javascript 68 | for (var i = 0; 10 > i; i--) { 69 | doSomething(i); 70 | } 71 | ``` 72 | 73 | ```typescript 74 | for (var i = 0; 10 > i; i++) { 75 | doSomething(i); 76 | } 77 | ``` 78 | 79 | ## Do not change `for` counter 80 | 81 | ```javascript 82 | for (var i = 0; i < 10; i++) {} 83 | ``` 84 | -------------------------------------------------------------------------------- /.grit/patterns/js/graphql_v3_csrf_prevention.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: GraphQL Sever v3 csrf prevention 3 | tags: [fix, graphql, security] 4 | --- 5 | 6 | The Apollo GraphQL server lacks the 'csrfPrevention' option. This option is 'false' by the default in v3 of the Apollo GraphQL v3, which can enable CSRF attacks. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | or { 14 | `new ApolloServer({$config})` where { 15 | $config <: not contains `csrfPrevention: $boolean`, 16 | $config += `csrfPrevention: true` 17 | }, 18 | `new ApolloServer({$config})` where { 19 | $config <: contains `csrfPrevention: false` => `csrfPrevention: true` 20 | } 21 | } 22 | ``` 23 | 24 | ## GraphQL Sever v3 csrf prevention 25 | 26 | ```javascript 27 | // BAD 1: Lacks 'csrfPrevention: true' 28 | const apollo_server_1 = new ApolloServer({ 29 | typeDefs, 30 | resolvers 31 | }); 32 | 33 | // BAD 2: Has 'csrfPrevention: false' 34 | const apollo_server_2 = new ApolloServer({ 35 | typeDefs, 36 | resolvers, 37 | csrfPrevention: false, 38 | }); 39 | 40 | // Good: Has 'csrfPrevention: true' 41 | const apollo_server_3 = new ApolloServer({ 42 | typeDefs, 43 | resolvers, 44 | csrfPrevention: true, 45 | }); 46 | ``` 47 | 48 | ```javascript 49 | // BAD 1: Lacks 'csrfPrevention: true' 50 | const apollo_server_1 = new ApolloServer({ 51 | typeDefs, 52 | resolvers, 53 | csrfPrevention: true 54 | }); 55 | 56 | // BAD 2: Has 'csrfPrevention: false' 57 | const apollo_server_2 = new ApolloServer({ 58 | typeDefs, 59 | resolvers, 60 | csrfPrevention: true, 61 | }); 62 | 63 | // Good: Has 'csrfPrevention: true' 64 | const apollo_server_3 = new ApolloServer({ 65 | typeDefs, 66 | resolvers, 67 | csrfPrevention: true, 68 | }); 69 | ``` 70 | -------------------------------------------------------------------------------- /.grit/patterns/js/graphql_v4_csrf_prevention.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: GraphQL Sever v4 csrf prevention 3 | tags: [fix, graphQL, security] 4 | --- 5 | 6 | The Apollo GraphQL server sets the 'csrfPrevention' option to false. This can enable CSRF attacks. 7 | 8 | - [reference](https://www.apollographql.com/docs/apollo-server/v3/security/cors/#preventing-cross-site-request-forgery-csrf) 9 | 10 | 11 | ```grit 12 | engine marzano(0.1) 13 | language js 14 | 15 | `new ApolloServer($config)` where { 16 | $config <: contains `csrfPrevention: false` => `csrfPrevention: true` 17 | } 18 | ``` 19 | 20 | ## GraphQL Sever v4 csrf prevention 21 | 22 | ```javascript 23 | // OK: Lacks 'csrfPrevention: true', but on v4 this option is true by default 24 | const apollo_server_1 = new ApolloServer({ 25 | typeDefs, 26 | resolvers, 27 | }); 28 | 29 | // Good: Has 'csrfPrevention: true' 30 | const apollo_server_3 = new ApolloServer({ 31 | typeDefs, 32 | resolvers, 33 | csrfPrevention: true, 34 | }); 35 | 36 | // BAD: Has 'csrfPrevention: false' 37 | const apollo_server_2 = new ApolloServer({ 38 | typeDefs, 39 | resolvers, 40 | csrfPrevention: false, 41 | }); 42 | ``` 43 | 44 | ```javascript 45 | // OK: Lacks 'csrfPrevention: true', but on v4 this option is true by default 46 | const apollo_server_1 = new ApolloServer({ 47 | typeDefs, 48 | resolvers, 49 | }); 50 | 51 | // Good: Has 'csrfPrevention: true' 52 | const apollo_server_3 = new ApolloServer({ 53 | typeDefs, 54 | resolvers, 55 | csrfPrevention: true, 56 | }); 57 | 58 | // BAD: Has 'csrfPrevention: false' 59 | const apollo_server_2 = new ApolloServer({ 60 | typeDefs, 61 | resolvers, 62 | csrfPrevention: true, 63 | }); 64 | ``` 65 | -------------------------------------------------------------------------------- /.grit/patterns/js/index_of_to_includes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rewrite `indexOf(...) === -1` ⇒ `includes` 3 | tags: [ES7, SE] 4 | --- 5 | 6 | ES7 introduced the `includes` method for arrays so bitwise and comparisons to `-1` are no longer needed. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | pattern index_of_like($container, $contained) { 14 | `$container.$method($contained)` where { 15 | $method <: or { 16 | `indexOf`, 17 | `lastIndexOf` 18 | } 19 | } 20 | } 21 | 22 | or { 23 | or { 24 | `$something === -1`, 25 | `$something == -1` 26 | } as $whole where { 27 | $something <: index_of_like($container, $contained), 28 | $whole => `!$container.includes($contained)` 29 | }, 30 | or { 31 | `$something !== -1`, 32 | `$something != -1`, 33 | `~$something` 34 | } as $whole where { 35 | $something <: index_of_like($container, $contained), 36 | $whole => `$container.includes($contained)` 37 | } 38 | } 39 | ``` 40 | 41 | ``` 42 | 43 | ``` 44 | 45 | ## Transforms indexOf 46 | 47 | ```javascript 48 | !~foo.indexOf("a"); 49 | 50 | foo.indexOf("a") === -1; 51 | 52 | foo.indexOf("a") == -1; 53 | 54 | foo.indexOf("a") !== -1; 55 | 56 | foo.indexOf("a") != -1; 57 | 58 | ~foo.indexOf("a"); 59 | ``` 60 | 61 | ```typescript 62 | !foo.includes("a"); 63 | 64 | !foo.includes("a") 65 | 66 | !foo.includes("a") 67 | 68 | foo.includes("a") 69 | 70 | foo.includes("a") 71 | 72 | foo.includes("a") 73 | ``` 74 | 75 | ## Transforms lastIndexOf 76 | 77 | ```javascript 78 | !~foo.lastIndexOf("a"); 79 | 80 | foo.lastIndexOf("a") === -1; 81 | 82 | foo.lastIndexOf("a") == -1; 83 | 84 | foo.lastIndexOf("a") !== -1; 85 | 86 | foo.lastIndexOf("a") != -1; 87 | 88 | ~foo.lastIndexOf("a"); 89 | ``` 90 | 91 | ```typescript 92 | !foo.includes("a"); 93 | 94 | !foo.includes("a") 95 | 96 | !foo.includes("a") 97 | 98 | foo.includes("a") 99 | 100 | foo.includes("a") 101 | 102 | foo.includes("a") 103 | ``` 104 | 105 | ## Does not change lastIndexOf or indexOf if it checks for a real index 106 | 107 | ```javascript 108 | foo.lastIndexOf("") == 1; 109 | 110 | foo.indexOf("") == 1; 111 | ``` 112 | -------------------------------------------------------------------------------- /.grit/patterns/js/inner_html_to_inner_text.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rewrite `innerHtml` ⇒ `innerText` 3 | tags: [security, fix] 4 | --- 5 | 6 | Replaces `innerHtml` with `innerText`, which is safer in most cases. 7 | 8 | See the [OWASP DOM XSS cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html#rule-1---html-escape-then-javascript-escape-before-inserting-untrusted-data-into-html-subcontext-within-the-execution-context). 9 | 10 | 11 | ```grit 12 | engine marzano(1.0) 13 | language js 14 | 15 | `$x.innerHtml` => `$x.innerText` 16 | ``` 17 | 18 | ## Transforms innerHtml to innerText 19 | 20 | ```javascript 21 | x.innerHtml = 'foo'; 22 | ``` 23 | 24 | ```typescript 25 | x.innerText = 'foo'; 26 | ``` 27 | -------------------------------------------------------------------------------- /.grit/patterns/js/jest_no_skipped_tests.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: [jest, testing, hygiene] 3 | --- 4 | 5 | # No skipped tests 6 | 7 | Disable skipping Jest tests without an explanation. 8 | 9 | 10 | ```grit 11 | engine marzano(0.1) 12 | language js 13 | 14 | `$testlike.skip` => `$testlike` where { 15 | $testlike <: not after comment(), 16 | $testlike <: or { 17 | `describe`, 18 | `it`, 19 | `test` 20 | } 21 | } 22 | ``` 23 | 24 | ## Forbidden 25 | 26 | ```js 27 | describe.skip('foo', () => { 28 | it('bar', () => { 29 | expect(true).toBe(true); 30 | }); 31 | }); 32 | ``` 33 | 34 | ```ts 35 | describe('foo', () => { 36 | it('bar', () => { 37 | expect(true).toBe(true); 38 | }); 39 | }); 40 | ``` 41 | 42 | ## Comment explanation 43 | 44 | If you include a comment explaining why the test is skipped, it will be allowed. 45 | 46 | ```js 47 | // This test flakes on CI 48 | describe.skip('foo', () => { 49 | it('bar', () => { 50 | expect(true).toBe(true); 51 | }); 52 | }); 53 | ``` 54 | -------------------------------------------------------------------------------- /.grit/patterns/js/langfuse_node_v2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Upgrade Langfuse to v2 3 | tags: [js, ts, npm, upgrade, langfuse, migration] 4 | --- 5 | 6 | Upgrade the Langfuse SDK to v2 following [this guide](https://langfuse.com/docs/sdk/typescript#upgrade1to2). 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | `$_.generation({ $params })` where { 14 | $params <: contains bubble pair($key) where { 15 | $key <: or { 16 | `prompt` => `input`, 17 | `completion` => `output` 18 | } 19 | }, 20 | $program <: contains or { 21 | import_statement($source) where { $source <: `'langfuse'` }, 22 | `$_ = require('langfuse')` 23 | } 24 | } 25 | ``` 26 | 27 | ## Rewrites generation parameters if there is a langfuse import 28 | 29 | ```js 30 | import { LangfuseGenerationClient } from 'langfuse'; 31 | import { messages, trace } from './messages'; 32 | 33 | const generation: LangfuseGenerationClient = trace.generation({ 34 | name: 'chat-completion', 35 | model: 'gpt-3.5-turbo', 36 | modelParameters: { 37 | temperature: 0.9, 38 | maxTokens: 2000, 39 | }, 40 | prompt: messages, 41 | completion: 'completion', 42 | }); 43 | ``` 44 | 45 | ```ts 46 | import { LangfuseGenerationClient } from 'langfuse'; 47 | import { messages, trace } from './messages'; 48 | 49 | const generation: LangfuseGenerationClient = trace.generation({ 50 | name: 'chat-completion', 51 | model: 'gpt-3.5-turbo', 52 | modelParameters: { 53 | temperature: 0.9, 54 | maxTokens: 2000, 55 | }, 56 | input: messages, 57 | output: 'completion', 58 | }); 59 | ``` 60 | 61 | ## Does nothing if there is no langfuse import 62 | 63 | ```js 64 | import { messages, trace } from './messages'; 65 | 66 | const generation = trace.generation({ 67 | name: 'chat-completion', 68 | model: 'gpt-3.5-turbo', 69 | modelParameters: { 70 | temperature: 0.9, 71 | maxTokens: 2000, 72 | }, 73 | prompt: messages, 74 | completion: 'completion', 75 | }); 76 | ``` 77 | -------------------------------------------------------------------------------- /.grit/patterns/js/marzano.grit: -------------------------------------------------------------------------------- 1 | engine marzano(0.1) 2 | language js 3 | 4 | pattern literal_value() { 5 | or { 6 | number(), 7 | string(), 8 | `null`, 9 | `undefined` 10 | } 11 | } 12 | 13 | pattern function_like($name, $args, $statements) { 14 | or { 15 | `function $name($args) { $statements }`, 16 | `($args) => { $statements }`, 17 | `($args) => $statements` 18 | } 19 | } 20 | 21 | pattern loop_like() { 22 | or { 23 | `for($a;$b;$c){$body}`, 24 | `$arr.forEach(($condition) => {$body})`, 25 | `while($condition){$body}`, 26 | `do {$body} while($condition)` 27 | } 28 | } 29 | 30 | // All core stdlib functions can be done here 31 | private pattern before_each_file_stdlib() { before_each_file_prep_imports() } 32 | 33 | private pattern after_each_file_stdlib() { 34 | and { after_each_file_handle_imports(), 35 | after_each_file_global_rewrites() } 36 | } 37 | 38 | // These could be redefined in the future (not presently supported) 39 | pattern before_each_file() { before_each_file_stdlib() } 40 | 41 | pattern after_each_file() { after_each_file_stdlib() } 42 | -------------------------------------------------------------------------------- /.grit/patterns/js/next13_links.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remove `` Tags From Link Components 3 | tags: [good] 4 | --- 5 | 6 | Migrate Link component children to Next13 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | `$body` where { 14 | $body <: contains `$link` => `$link` 15 | } 16 | ``` 17 | 18 | ## Remove `` from `Link` component 19 | 20 | ```javascript 21 | 22 | https://leerob.io 23 | 24 | ``` 25 | 26 | ```typescript 27 | https://leerob.io 28 | ``` 29 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_alert.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remove `alert` statement 3 | tags: [fix] 4 | --- 5 | 6 | JavaScript’s alert is often used while debugging code, which should be removed before deployment to production. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | call_expression(function=`alert`) => . 14 | ``` 15 | 16 | ## Remove alert, confirm and prompt 17 | 18 | ```typescript 19 | alert('here!'); 20 | customAlert('Something happened!'); 21 | ``` 22 | 23 | ```typescript 24 | customAlert('Something happened!'); 25 | ``` 26 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_anonymous_default_export.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rename anonymous default export functions ⇒ main 3 | tags: [syntax] 4 | --- 5 | 6 | Replaces `export default function () { }` with `export default function main () { }` and `export default () => { }` with `const main = () => { }; export default main` 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | or { 14 | `export default async function($args) { $body }` => `export default async function main($args) { $body }`, 15 | `export default function($args) { $body }` => `export default function main($args) { $body }`, 16 | `export default $f` => `const main = $f;\nexport default main` where { 17 | $f <: `($args) => { $body }` 18 | } 19 | } 20 | ``` 21 | 22 | ## Name synchronous function declaration main 23 | 24 | ```javascript 25 | export default function () { 26 | console.log('test'); 27 | } 28 | ``` 29 | 30 | ```typescript 31 | export default function main() { 32 | console.log('test'); 33 | } 34 | ``` 35 | 36 | ## Name asynchronous function declaration main 37 | 38 | ```javascript 39 | export default async function (test) { 40 | console.log(test); 41 | } 42 | ``` 43 | 44 | ```typescript 45 | export default async function main(test) { 46 | console.log(test); 47 | } 48 | ``` 49 | 50 | ## Name arrow function main 51 | 52 | ```javascript 53 | export default async (test) => { 54 | console.log('test'); 55 | }; 56 | ``` 57 | 58 | ```typescript 59 | const main = async (test) => { 60 | console.log('test'); 61 | }; 62 | export default main; 63 | ``` 64 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_array_constructor.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rewrite `Array(a, b, ...)` ⇒ `[a, b, ...]` 3 | tags: [fix] 4 | --- 5 | 6 | The literal notation avoids the single-argument pitfall or the Array global being redefined. 7 | 8 | Use of the Array constructor to create a new array is discouraged in favor of array literal notation, i.e., `[a, b, ...]`. The exception is when the Array constructor is used to intentionally create sparse arrays of a specified size by giving the constructor a single numeric argument. 9 | 10 | 11 | ```grit 12 | engine marzano(0.1) 13 | language js 14 | 15 | or { 16 | `new Array($args)` => `[$args]`, 17 | `Array($args)` => `[$args]` 18 | } where { $args <: [$_, $_, ...] } 19 | ``` 20 | 21 | ``` 22 | 23 | ``` 24 | 25 | ## Transform Array constructor to Array object. 26 | 27 | ```javascript 28 | Array(0, 1, 2); 29 | ``` 30 | 31 | ```typescript 32 | [0, 1, 2] 33 | ``` 34 | 35 | ## Transform Array constructor using `new` to Array object. 36 | 37 | ```javascript 38 | new Array(0, 1, 2); 39 | ``` 40 | 41 | ```typescript 42 | [0, 1, 2] 43 | ``` 44 | 45 | ## Don't transform Array constructor. 46 | 47 | ```javascript 48 | Array(500); 49 | ``` 50 | 51 | ## Don't transform Array constructor using `new`. 52 | 53 | ```javascript 54 | new Array(someOtherArray.length); 55 | ``` 56 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_bitwise.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rewrite `&` ⇒ `&&`, `|` ⇒ `||` 3 | --- 4 | 5 | Bitwise operators `&` or `|` are often used by mistake instead of `&&` or `||`, which can cause unexpected errors. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language js 10 | 11 | or { 12 | `$x & $y` => `$x && $y`, 13 | `$x | $y` => `$x || $y` 14 | } 15 | ``` 16 | 17 | ## `&` ⇒ `&&` 18 | 19 | ```javascript 20 | var z = x & y; 21 | ``` 22 | 23 | ```typescript 24 | var z = x && y; 25 | ``` 26 | 27 | ## `|` ⇒ `||` 28 | 29 | ```javascript 30 | var x = y | z; 31 | ``` 32 | 33 | ```typescript 34 | var x = y || z; 35 | ``` 36 | 37 | ## Do no change `&&` and `||` operators 38 | 39 | ```javascript 40 | var c = a && b; 41 | var k = p || t; 42 | ``` 43 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_caller.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remove `arguments.caller` and `arguments.callee` 3 | tags: [fix] 4 | --- 5 | 6 | `arguments.caller` and `arguments.called` have been deprecated. 7 | 8 | They make code optimizations difficult and their use is forbidden in ECMAScript 5 while in strict mode. 9 | 10 | 11 | ```grit 12 | engine marzano(0.1) 13 | language js 14 | 15 | or { 16 | `function $name($_) { $body }` where { 17 | $body <: contains { `arguments.callee` => `$name` } 18 | }, 19 | `function $name($_){ $body }` where { 20 | $body <: contains { `arguments.caller` => `$name.caller` } 21 | } 22 | } 23 | ``` 24 | 25 | ## Remove arguments.callee 26 | 27 | ```javascript 28 | function factorial(n) { 29 | return n == 1 ? 1 : n * arguments.callee(n - 1); 30 | } 31 | ``` 32 | 33 | ```typescript 34 | function factorial(n) { 35 | return n == 1 ? 1 : n * factorial(n - 1); 36 | } 37 | ``` 38 | 39 | ## Remove arguments.caller 40 | 41 | ```javascript 42 | function whoCalled() { 43 | if (arguments.caller == null) alert("Call from the global scope."); 44 | else alert(arguments.caller + " call me!"); 45 | } 46 | ``` 47 | 48 | ```typescript 49 | function whoCalled() { 50 | if (whoCalled.caller == null) alert("Call from the global scope."); 51 | else alert(whoCalled.caller + " call me!"); 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_confirm.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remove `confirm` statement 3 | tags: [fix] 4 | --- 5 | 6 | JavaScript’s confirm function is widely considered to be obtrusive as UI elements and should be replaced by a more appropriate custom UI implementation. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | call_expression(function=`confirm`) => . 14 | ``` 15 | 16 | ## Remove alert, confirm and prompt 17 | 18 | ```typescript 19 | confirm('Are you sure?'); 20 | customConfirm('Are you sure?'); 21 | ``` 22 | 23 | ```typescript 24 | customConfirm('Are you sure?'); 25 | ``` 26 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_console_log.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remove `console.log` 3 | tags: [good] 4 | --- 5 | 6 | Remove `console.log` statements. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | `console.log($arg)` => . where { $arg <: not within catch_clause() } 14 | ``` 15 | 16 | ## Removes a simple `console.log` statement 17 | 18 | ```javascript 19 | // Do not remove this 20 | console.error('foo'); 21 | console.log('foo'); 22 | ``` 23 | 24 | ```javascript 25 | // Do not remove this 26 | console.error('foo'); 27 | ``` 28 | 29 | ## Removes the statement in a function 30 | 31 | ```javascript 32 | function f() { 33 | console.log('foo'); 34 | } 35 | ``` 36 | 37 | ```typescript 38 | function f() {} 39 | ``` 40 | 41 | ## Works in a list as well 42 | 43 | ```javascript 44 | server.listen(PORT, console.log(`Server started on port ${PORT}`)); 45 | ``` 46 | 47 | ```typescript 48 | server.listen(PORT); 49 | ``` 50 | 51 | ## Doesn't remove `console.log` in a catch clause 52 | 53 | ```javascript 54 | try { 55 | } catch (e) { 56 | console.log('foo'); 57 | } 58 | ``` 59 | 60 | ## Works on multiple console logs in the same file 61 | 62 | ```javascript 63 | // Do not remove this 64 | console.error('foo'); 65 | console.log('foo'); 66 | console.log('bar'); 67 | ``` 68 | 69 | ```javascript 70 | // Do not remove this 71 | console.error('foo'); 72 | ``` 73 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_dead_code.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remove unreachable code 3 | tags: [good, SE] 4 | --- 5 | 6 | Remove unreachable code found after `return` / `throw` / `continue` or `break` statements. 7 | 8 | ```grit 9 | engine marzano(0.1) 10 | language js 11 | 12 | statement_block($statements) where { 13 | $deleting = "false", 14 | $statements <: some bubble($deleting) $s where { 15 | if ($deleting <: "true") { $s => . } else { 16 | // we start deleting 17 | if ($s <: or { 18 | throw_statement(), 19 | continue_statement(), 20 | return_statement() 21 | }) { $deleting = "true" } 22 | } 23 | } 24 | } 25 | ``` 26 | 27 | ## Remove code after return 28 | 29 | ```javascript 30 | function f() { 31 | return 3; 32 | 1 + 1; 33 | } 34 | ``` 35 | 36 | ```typescript 37 | function f() { 38 | return 3; 39 | } 40 | ``` 41 | 42 | ## Remove code after return, multiline 43 | 44 | ```javascript 45 | function f() { 46 | foo(); 47 | return 3; 48 | 1 + 1; 49 | } 50 | ``` 51 | 52 | ```typescript 53 | function f() { 54 | foo(); 55 | return 3; 56 | } 57 | ``` 58 | 59 | ## Don't exit a scope 60 | 61 | ```javascript 62 | function f() { 63 | if (a) { 64 | return 3; 65 | } 66 | 1 + 1; 67 | } 68 | ``` 69 | 70 | ```typescript 71 | function f() { 72 | if (a) { 73 | return 3; 74 | } 75 | 1 + 1; 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_debugger.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remove `debugger` statement 3 | tags: [fix] 4 | --- 5 | 6 | The code in production should not contain a `debugger`. It causes the browser to stop executing the code and open the debugger. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | debugger_statement() => . 14 | ``` 15 | 16 | ``` 17 | 18 | ``` 19 | 20 | ## Remove debugger 21 | 22 | ```javascript 23 | function isTruthy(x) { 24 | debugger; 25 | return Boolean(x); 26 | } 27 | ``` 28 | 29 | ```typescript 30 | function isTruthy(x) { 31 | return Boolean(x); 32 | } 33 | ``` 34 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_eq_null.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Compare `null` using `===` or `!==` 3 | tags: [good] 4 | --- 5 | 6 | Comparing to `null` needs a type-checking operator (=== or !==), to avoid incorrect results when the value is `undefined`. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | // We use the syntax-tree node binary_expression to capture all expressions where $a and $b are operated on by "==" or "!=". 14 | // This code takes advantage of Grit's allowing us to nest rewrites inside match conditions and to match syntax-tree fields on patterns. 15 | binary_expression($operator, $left, $right) where { 16 | $operator <: or { 17 | "==" => `===`, 18 | "!=" => `!==` 19 | }, 20 | or { $left <: `null`, $right <: `null` } 21 | } 22 | ``` 23 | 24 | ``` 25 | 26 | ``` 27 | 28 | ## `$val == null` => `$val === null` 29 | 30 | ```javascript 31 | if (val == null) { 32 | done(); 33 | } 34 | ``` 35 | 36 | ```typescript 37 | if (val === null) { 38 | done(); 39 | } 40 | ``` 41 | 42 | ## `$val != null` => `$val !== null` 43 | 44 | ```javascript 45 | if (val != null) { 46 | done(); 47 | } 48 | ``` 49 | 50 | ```typescript 51 | if (val !== null) { 52 | done(); 53 | } 54 | ``` 55 | 56 | ## `$val != null` => `$val !== null` into `while` 57 | 58 | ```javascript 59 | while (val != null) { 60 | did(); 61 | } 62 | ``` 63 | 64 | ```typescript 65 | while (val !== null) { 66 | did(); 67 | } 68 | ``` 69 | 70 | ## Do not change `$val === null` 71 | 72 | ```javascript 73 | if (val === null) { 74 | done(); 75 | } 76 | ``` 77 | 78 | ## Do not change `$val !== null` 79 | 80 | ``` 81 | while (val !== null) { 82 | doSomething(); 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_inline_if.md: -------------------------------------------------------------------------------- 1 | # No inline `if` and `else` statements 2 | 3 | The `if` and `else` statements should not be used inline. Instead, use a block statement. 4 | 5 | ```grit 6 | engine marzano(0.1) 7 | language js 8 | 9 | `if ($cond) $then 10 | else $else` => `if ($cond) { 11 | $then 12 | } else { 13 | $else 14 | }` where { $then <: not statement_block(), $else <: not statement_block() } 15 | ``` 16 | 17 | ## Examples 18 | 19 | ```javascript 20 | if (condition) doSomething(); 21 | else doSomethingElse(); 22 | ``` 23 | 24 | ```javascript 25 | if (condition) { 26 | doSomething(); 27 | } else { 28 | doSomethingElse(); 29 | } 30 | ``` 31 | 32 | ## Good 33 | 34 | ```javascript 35 | if (condition) { 36 | doSomething(); 37 | } else { 38 | doSomethingElse(); 39 | } 40 | ``` 41 | 42 | ## If by itself 43 | 44 | This pattern only targets `if` statements that are followed by an `else` statement. If the `if` statement is by itself, it is not affected. 45 | 46 | ```javascript 47 | if (condition) doSomething(); 48 | ``` 49 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_iterator.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rewrite `__iterator__` property ⇒ `_iterator_` 3 | tags: [good] 4 | --- 5 | 6 | Use `_iterator_` instead of `__iterator__`. `__iterator__` is obsolete and is not implemented by all browsers. 7 | 8 | 9 | ```grit 10 | engine marzano(1.0) 11 | language js 12 | 13 | or { 14 | `$obj.prototype.__iterator__` => `$obj._iterator_`, 15 | `$obj.prototype["__iterator__"]` => `$obj._iterator_`, 16 | `$obj.prototype['__iterator__']` => `$obj._iterator_`, 17 | `$obj.__iterator__` => `$obj._iterator_`, 18 | `$obj["__iterator__"]` => `$obj._iterator_`, 19 | `$obj['__iterator__']` => `$obj._iterator_` 20 | } 21 | ``` 22 | 23 | ## `prototype.__iterator__` => `_iterator_` 24 | 25 | ```javascript 26 | Data.prototype.__iterator__ = function () { 27 | return new DataIterator(this); 28 | }; 29 | 30 | var __iterator__ = function () { 31 | doIterator(); 32 | }; 33 | ``` 34 | 35 | ``` 36 | Data._iterator_ = function () { 37 | return new DataIterator(this); 38 | }; 39 | 40 | var __iterator__ = function () { 41 | doIterator(); 42 | }; 43 | ``` 44 | 45 | ## `prototype["__iterator__"]` property => `_iterator_` 46 | 47 | ```javascript 48 | Data.prototype['__iterator__'] = function () { 49 | return new DataIterator(this); 50 | }; 51 | ``` 52 | 53 | ```typescript 54 | Data._iterator_ = function () { 55 | return new DataIterator(this); 56 | }; 57 | ``` 58 | 59 | ## `__iterator__` => `_iterator_` 60 | 61 | ```javascript 62 | bar.__iterator__ = function () { 63 | doIterator(); 64 | }; 65 | ``` 66 | 67 | ```typescript 68 | bar._iterator_ = function () { 69 | doIterator(); 70 | }; 71 | ``` 72 | 73 | ## `['__iterator__']` property => `_iterator_` 74 | 75 | ```javascript 76 | bar['__iterator__'] = function () { 77 | doIterator(); 78 | }; 79 | ``` 80 | 81 | ```typescript 82 | bar._iterator_ = function () { 83 | doIterator(); 84 | }; 85 | ``` 86 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_new_object.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rewrite `new Object()` ⇒ `{}` 3 | tags: [good, syntax] 4 | --- 5 | 6 | The `{}` literal form is a more concise way of creating an object. 7 | 8 | There is no performance difference. 9 | 10 | 11 | ```grit 12 | engine marzano(0.1) 13 | language js 14 | 15 | or { 16 | `new Object()` => `{}`, 17 | `new Object` => `{}` 18 | } 19 | ``` 20 | 21 | ## Object constructors to literal syntax 22 | 23 | ```javascript 24 | var myObject = new Object(); 25 | ``` 26 | 27 | ```typescript 28 | var myObject = {}; 29 | ``` 30 | 31 | ## Don't change object constructors to literal syntax 32 | 33 | ```javascript 34 | var myObject = new CustomObject(); 35 | ``` 36 | 37 | ## Don't change object constructors 38 | 39 | ```javascript 40 | var myObject = {}; 41 | ``` 42 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_new_symbol.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rewrite `new Symbol` ⇒ `Symbol` 3 | tags: [good] 4 | --- 5 | 6 | Calling `Symbol` with the `new` operator throws a `TypeError` exception. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | `new $sym($x)` => `$sym($x)` where { 14 | $sym <: `Symbol`, 15 | // make sure it is the pre-defined Symbol, avoid rewriting if `Symbol` is redefined by user 16 | $program <: not contains `Symbol = $_` 17 | } 18 | ``` 19 | 20 | ## Remove `new` from `Symbol` constructor 21 | 22 | ```javascript 23 | var bar = new Symbol('bar'); 24 | ``` 25 | 26 | ```typescript 27 | var bar = Symbol('bar'); 28 | ``` 29 | 30 | ## Do not remove `new` from shadowed `Symbol` constructor 31 | 32 | ```javascript 33 | function woo(Symbol) { 34 | const foo = new Symbol('foo'); 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_prompt.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remove `prompt` statement 3 | tags: [fix] 4 | --- 5 | 6 | JavaScript’s prompt function is widely considered to be obtrusive as UI elements and should be replaced by a more appropriate custom UI implementation. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | call_expression(function=`prompt`) => . 14 | ``` 15 | 16 | ## Remove alert, confirm and prompt 17 | 18 | ```typescript 19 | prompt("What's your name?", 'John Doe'); 20 | customPrompt('Who are you?'); 21 | ``` 22 | 23 | ```typescript 24 | customPrompt('Who are you?'); 25 | ``` 26 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_prototype_builtins.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Prototype methods ⇒ `Object.prototype` methods 3 | tags: [fix] 4 | --- 5 | 6 | Call `hasOwnProperty`, `isPrototypeOf`, `propertyIsEnumerable` methods only from `Object.prototype`. 7 | Otherwise it can cause errors. 8 | 9 | 10 | ```grit 11 | engine marzano(0.1) 12 | language js 13 | 14 | `$obj.$method($arg)` => `Object.prototype.$method.call($obj, $arg)` where { 15 | $method <: or { 16 | `hasOwnProperty`, 17 | `isPrototypeOf`, 18 | `propertyIsEnumerable` 19 | } 20 | } 21 | ``` 22 | 23 | ## Calling `hasOwnProperty` method from `Object.prototype` 24 | 25 | ```javascript 26 | var hasProperty = woo.hasOwnProperty("top"); 27 | ``` 28 | 29 | ```typescript 30 | var hasProperty = Object.prototype.hasOwnProperty.call(woo, "top"); 31 | ``` 32 | 33 | ## Calling `isPrototypeOf` method from `Object.prototype` 34 | 35 | ```javascript 36 | var isPrototypeOf = woo.isPrototypeOf(top); 37 | ``` 38 | 39 | ```typescript 40 | var isPrototypeOf = Object.prototype.isPrototypeOf.call(woo, top); 41 | ``` 42 | 43 | ## Calling `propertyIsEnumerable` method from `Object.prototype` 44 | 45 | ```javascript 46 | var isEnumerable = woo.propertyIsEnumerable("top"); 47 | ``` 48 | 49 | ```typescript 50 | var isEnumerable = Object.prototype.propertyIsEnumerable.call(woo, "top"); 51 | ``` 52 | 53 | ## Do not change methods for `{}` object 54 | 55 | ```javascript 56 | var isPrototypeOf = {}.isPrototypeOf.call(woo, top); 57 | ``` 58 | 59 | ## Do not change methods for `Object.prototype` 60 | 61 | ```javascript 62 | var hasProperty = Object.prototype.hasOwnProperty.call(woo, "top"); 63 | ``` 64 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_restricted.grit: -------------------------------------------------------------------------------- 1 | language js 2 | 3 | pattern no_restricted_imports($modules) { 4 | file($body) where { $body <: contains `import $_ from "$src"` } 5 | } 6 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_unsafe_negation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rewrite `!key in col` ⇒ `!(key in col)` 3 | tags: [fix] 4 | --- 5 | 6 | Negates `key` instead of the entire expression, which is likely a bug. 7 | 8 | The intent is usually to negate the entire relation expression. 9 | 10 | For `!key in foo`, operator precedence makes it equivalent to `(!key) in foo` and, type conversion makes it equivalent to `(key ? "false" : "true") in foo`. 11 | 12 | For `!obj instanceof Ctor`, operator precedence makes it equivalent to `(!obj) instanceof Ctor` which is always `false` since boolean values are not objects. 13 | 14 | 15 | ```grit 16 | engine marzano(0.1) 17 | language js 18 | 19 | or { 20 | `!$key in $collection` => `!($key in $collection)`, 21 | `!$key instanceof $collection` => `!($key instanceof $collection)` 22 | } 23 | ``` 24 | 25 | ## Transforms when relational operator `in` is not encapsulated in an if statement 26 | 27 | ```javascript 28 | if (!key in foo) { 29 | foo(); 30 | } 31 | ``` 32 | 33 | ```typescript 34 | if (!(key in foo)) { 35 | foo(); 36 | } 37 | ``` 38 | 39 | ## Transforms when relational operator `in` is not encapsulated in an else if statement 40 | 41 | ```javascript 42 | if (!(key in foo)) { 43 | foo(); 44 | } else if (true && !key in bar) { 45 | foo(); 46 | } 47 | ``` 48 | 49 | ```typescript 50 | if (!(key in foo)) { 51 | foo(); 52 | } else if (true && !(key in bar)) { 53 | foo(); 54 | } 55 | ``` 56 | 57 | ## Transforms when relational operator `instanceof` is not encapsulated in an if statement 58 | 59 | ```javascript 60 | if (!obj instanceof Ctor) { 61 | foo(); 62 | } 63 | ``` 64 | 65 | ```typescript 66 | if (!(obj instanceof Ctor)) { 67 | foo(); 68 | } 69 | ``` 70 | 71 | ## Transforms when relational operator `instanceof` is not encapsulated in a ternary operation 72 | 73 | ```javascript 74 | !obj instanceof Ctor ? foo : bar; 75 | ``` 76 | 77 | ```typescript 78 | !(obj instanceof Ctor) ? foo : bar; 79 | ``` 80 | 81 | ## Does Not transform when type conversion is explicit 82 | 83 | ```javascript 84 | if ("" + !key in object) { 85 | // make operator precedence and type conversion explicit 86 | // in a rare situation when that is the intended meaning 87 | } 88 | ``` 89 | -------------------------------------------------------------------------------- /.grit/patterns/js/no_yoda_conditions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Yoda conditions not 3 | tags: [good, syntax] 4 | --- 5 | 6 | Prefer natural language style conditions in favour of Yoda style conditions. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | or { 14 | `$x == $y` => `$y == $x`, 15 | `$x === $y` => `$y === $x`, 16 | `$x > $y` => `$y < $x`, 17 | `$x < $y` => `$y > $x`, 18 | `$x >= $y` => `$y <= $x`, 19 | `$x <= $y` => `$y >= $x` 20 | } where { 21 | // In order to capture a yoda condition, the LHS $x must be a LiteralValue and the RHS $y must not be one 22 | $x <: literal(), 23 | ! $y <: literal() 24 | } 25 | ``` 26 | 27 | ## Change in if 28 | 29 | ```javascript 30 | if (42 == x) { 31 | } 32 | ``` 33 | 34 | ```typescript 35 | if (x == 42) { 36 | } 37 | ``` 38 | 39 | ## Change in while 40 | 41 | ```javascript 42 | while (42 == x) {} 43 | ``` 44 | 45 | ```typescript 46 | while (x == 42) {} 47 | ``` 48 | 49 | ## Change in for 50 | 51 | ```javascript 52 | while (42 == x) {} 53 | ``` 54 | 55 | ```typescript 56 | while (x == 42) {} 57 | ``` 58 | 59 | ## Leave non literal alone 60 | 61 | ```javascript 62 | if (foo() == x) { 63 | } 64 | ``` 65 | 66 | ## Reverse less than 67 | 68 | ```javascript 69 | foo(10 < x); 70 | ``` 71 | 72 | ```typescript 73 | foo(x > 10); 74 | ``` 75 | 76 | ## Avoid `in` 77 | 78 | ```javascript 79 | foo('foo' in c); 80 | ``` 81 | -------------------------------------------------------------------------------- /.grit/patterns/js/prefer_is_nan.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rewrite `=== NaN` ⇒ `isNaN` 3 | tags: [fix] 4 | --- 5 | 6 | Convert comparisons to `NaN` (e.g., `x == NaN`) to use `isNaN` (e.g., `isNaN(x)`). 7 | 8 | In JavaScript, `NaN` is a special value of `Number` type. It’s used to represent any of the _not-a-number_ values represented by the double-precision 64-bit format as specified by the IEEE Standard for Binary Floating-Point Arithmetic. 9 | 10 | `NaN` is unique in JavaScript by not being equal to anything, including itself, so it does not make sense to compare to it. 11 | 12 | 13 | ```grit 14 | engine marzano(0.1) 15 | language js 16 | 17 | pattern any_equals($a, $b) { 18 | or { 19 | `$a == $b`, 20 | `$a === $b`, 21 | `$b == $a`, 22 | `$b === $a` 23 | } 24 | } 25 | 26 | pattern any_not_equals($a, $b) { 27 | or { 28 | binary_expression(operator=or { 29 | `!==`, 30 | `!=` 31 | }, left=$a, right=$b), 32 | binary_expression(operator=or { 33 | `!==`, 34 | `!=` 35 | }, left=$b, right=$a) 36 | } 37 | } 38 | 39 | or { 40 | any_equals(a=`NaN`, $b) => `isNaN($b)`, 41 | any_not_equals(a=`NaN`, $b) => `!isNaN($b)` 42 | } 43 | ``` 44 | 45 | ## Converts double equality check 46 | 47 | ```javascript 48 | if (foo == NaN) { 49 | } 50 | ``` 51 | 52 | ```typescript 53 | if (isNaN(foo)) { 54 | } 55 | ``` 56 | 57 | ## Converts triple equality check 58 | 59 | ```javascript 60 | if (foo === NaN) { 61 | } 62 | ``` 63 | 64 | ```typescript 65 | if (isNaN(foo)) { 66 | } 67 | ``` 68 | 69 | ## Converts double inequality check 70 | 71 | ```javascript 72 | if (foo != NaN) { 73 | } 74 | ``` 75 | 76 | ``` 77 | if (!isNaN(foo)) { 78 | } 79 | ``` 80 | 81 | ## Converts triple inequality check 82 | 83 | ```javascript 84 | if (foo !== NaN) { 85 | } 86 | ``` 87 | 88 | ```typescript 89 | if (!isNaN(foo)) { 90 | } 91 | ``` 92 | 93 | ## Doesn't convert assignments 94 | 95 | ```javascript 96 | var x = NaN; 97 | ``` 98 | -------------------------------------------------------------------------------- /.grit/patterns/js/react.grit: -------------------------------------------------------------------------------- 1 | engine marzano(0.1) 2 | language js 3 | 4 | pattern ReactNode($name, $props, $children) { 5 | or { 6 | `<$name $props>$children`, 7 | `<$name $props />` 8 | } 9 | } 10 | 11 | pattern react_functional_component($props, $body) { 12 | or { 13 | `const $name = ($props) => {$body}`, 14 | `const $name: React.FC<$propsType> = ($props) => {$body}`, 15 | `function $func($props) {$body}`, 16 | `function $func($props: $propsType): $returnType {$body}` 17 | } 18 | } 19 | 20 | pattern react_class_component($props, $body) { 21 | or { 22 | `class $ClassComponent {$body}`, 23 | `class $ClassComponent extends $ComponentName {$body}`, 24 | `class $ClassComponent<$props> {$body}`, 25 | `class $ClassComponent extends $ComponentName<$props> {$body}` 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.grit/patterns/js/react_named_imports.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Replace React default imports with destructured named imports 3 | tags: [import, react, global] 4 | --- 5 | 6 | This pattern replaces React default import method references (e.g. `React.ReactNode`) with destructured named imports (`import { ReactNode } from 'react'`). 7 | Running this will also make sure that `React` is imported. 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js(jsx) 12 | 13 | `React.$reactImport` where { 14 | $reactImport <: ensure_import_from(`"react"`) 15 | } => `$reactImport` 16 | ``` 17 | 18 | ## Replace method on React default import 19 | 20 | Given the following interface with `React` global: 21 | 22 | ```typescript 23 | import React from 'react'; 24 | 25 | interface MyComponentProps { 26 | children: React.ReactNode; 27 | anotherProp?: boolean; 28 | } 29 | ``` 30 | 31 | The result should import the relevant module: 32 | 33 | ```typescript 34 | import React from 'react'; 35 | import { ReactNode } from 'react'; 36 | 37 | interface MyComponentProps { 38 | children: ReactNode; 39 | anotherProp?: boolean; 40 | } 41 | ``` 42 | 43 | Patterns such as `unused_imports` can be used to clean up the duplicate import. 44 | 45 | ## Replace React global 46 | 47 | Given the following interface with `React` global: 48 | 49 | ```typescript 50 | interface MyComponentProps { 51 | children: React.ReactNode; 52 | anotherProp?: boolean; 53 | } 54 | ``` 55 | 56 | The result should import the relevant module: 57 | 58 | ```typescript 59 | import { ReactNode } from 'react'; 60 | 61 | interface MyComponentProps { 62 | children: ReactNode; 63 | anotherProp?: boolean; 64 | } 65 | ``` 66 | -------------------------------------------------------------------------------- /.grit/patterns/js/remove_apollo_graphql_schema_directives_for_v3_v4.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remove Apollo Graphql Schema Directives while migrating from v2 to v3 or v4 3 | tags: [fix, migration] 4 | --- 5 | 6 | The 'schemaDirectives' option in Apollo GraphQL, which was effective in ApolloServer version v2, no longer functions in versions >=3 and above. This change can have significant implications, potentially exposing authenticated endpoints, disabling rate limiting, and more, depending on the directives used. To address this, it is recommended to consult the references on creating custom directives specifically for ApolloServer versions v3 and v4. 7 | 8 | [references](https://www.apollographql.com/docs/apollo-server/schema/directives/#custom-directives) 9 | 10 | 11 | ```grit 12 | engine marzano(0.1) 13 | language js 14 | 15 | `new ApolloServer($props)` where { 16 | $props <: contains `schemaDirectives: {$schema}` => . 17 | } 18 | ``` 19 | 20 | ## Apollo Graphql Schema Directives while migrating from v2 to v3 or v4 21 | 22 | ```javascript 23 | // BAD: Has 'schemaDirectives' 24 | const apollo_server_1 = new ApolloServer({ 25 | typeDefs, 26 | resolvers, 27 | schemaDirectives: { 28 | rateLimit: rateLimitDirective 29 | }, 30 | }); 31 | 32 | // Good: Does not have 'schemaDirectives' 33 | const apollo_server_3 = new ApolloServer({ 34 | typeDefs, 35 | resolvers, 36 | }); 37 | ``` 38 | 39 | ```javascript 40 | // BAD: Has 'schemaDirectives' 41 | const apollo_server_1 = new ApolloServer({ 42 | typeDefs, 43 | resolvers, 44 | 45 | }); 46 | 47 | // Good: Does not have 'schemaDirectives' 48 | const apollo_server_3 = new ApolloServer({ 49 | typeDefs, 50 | resolvers, 51 | }); 52 | ``` 53 | -------------------------------------------------------------------------------- /.grit/patterns/js/remove_escape_markup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remove `.escapeMarkup = false` 3 | tags: [security, fix] 4 | --- 5 | 6 | Some template engines allow disabling HTML escaping, which can allow XSS vulnerabilities. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | `$object.escapeMarkup = false` => . 14 | ``` 15 | 16 | ## Removes `.escapeMarkup = false` 17 | 18 | ```javascript 19 | something; 20 | object.escapeMarkup = false; 21 | something(els); 22 | 23 | object.escapeMarkup = false; 24 | ``` 25 | 26 | ```typescript 27 | something; 28 | something(els); 29 | ``` 30 | -------------------------------------------------------------------------------- /.grit/patterns/js/remove_should_component_update_from_pure_components.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rewrite `shouldComponentUpdate` ⇒ `.` 3 | tags: [fix, React] 4 | --- 5 | 6 | Remove the `shouldComponentUpdate` method from `PureComponent`. `PureComponent` already has an implementation. 7 | 8 | `PureComponent` provides an implementation for `shouldComponentUpdate` which compares props by reference to determine if they have changed. 9 | 10 | 11 | ```grit 12 | engine marzano(0.1) 13 | language js 14 | 15 | class_declaration($heritage, $body) where { 16 | $heritage <: contains "PureComponent", 17 | $body <: contains `shouldComponentUpdate($_) { $_ }` => . 18 | } 19 | ``` 20 | 21 | ## Removes the entire shouldComponentUpdate() when extending React.PureComponent 22 | 23 | ```javascript 24 | class Foo extends React.PureComponent { 25 | customMethod() {} 26 | 27 | shouldComponentUpdate() {} 28 | 29 | render() { 30 | return ; 31 | } 32 | } 33 | ``` 34 | 35 | ```typescript 36 | class Foo extends React.PureComponent { 37 | customMethod() {} 38 | 39 | render() { 40 | return ; 41 | } 42 | } 43 | ``` 44 | 45 | ## Removes the entire shouldComponentUpdate() when extending destructured PureComponent 46 | 47 | ```javascript 48 | class Foo extends PureComponent { 49 | customMethod() {} 50 | 51 | shouldComponentUpdate() {} 52 | 53 | render() { 54 | return ; 55 | } 56 | } 57 | ``` 58 | 59 | ```typescript 60 | class Foo extends PureComponent { 61 | customMethod() {} 62 | 63 | render() { 64 | return ; 65 | } 66 | } 67 | ``` 68 | 69 | ## Does nothing when extending React.Component 70 | 71 | ```javascript 72 | class Foo extends React.Component { 73 | customMethod() {} 74 | 75 | shouldComponentUpdate() {} 76 | 77 | render() { 78 | return ; 79 | } 80 | } 81 | ``` 82 | -------------------------------------------------------------------------------- /.grit/patterns/js/replaceAll_to_replace.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rewrite `replaceAll` ⇒ `replace` when have regex pattern 3 | tags: [fix] 4 | --- 5 | 6 | Replaces `replaceAll` with `replace`, when it uses a regex pattern. 7 | 8 | The `replaceAll` string method may not be supported in all JavaScript versions and older browsers. It is advisable to use the `replace()` method with a regular expression as the first argument. For example, use `mystring.replace(/bad/g, "good") `instead of `mystring.replaceAll("bad", "good")` 9 | 10 | - [reference](https://discourse.threejs.org/t/replaceall-is-not-a-function/14585) 11 | 12 | 13 | ```grit 14 | engine marzano(0.1) 15 | language js 16 | 17 | `$string.replaceAll("$stringValue", $replaceString)` => `$string.replace(/$stringValue/g, $replaceString)` 18 | ``` 19 | 20 | ## Transforms replaceAll to replace when have regex pattern 21 | 22 | ```javascript 23 | const str = 'Hello String'; 24 | // GOOD: replaceAll 25 | const str1 = old_str1.replaceAll(str, ' '); 26 | // GOOD: replaceAll 27 | const str1 = old_str1.replaceAll(hello, ' '); 28 | // BAD: replaceAll 29 | const str2 = old_str2.replaceAll('\t', ' '); 30 | // GOOD: replaceAll 31 | const str3 = old_str3.replace('\t', ' '); 32 | // BAD: replaceAll 33 | const mystr = mystring.replaceAll('bad', 'good'); 34 | ``` 35 | 36 | ```javascript 37 | const str = 'Hello String'; 38 | // GOOD: replaceAll 39 | const str1 = old_str1.replaceAll(str, ' '); 40 | // GOOD: replaceAll 41 | const str1 = old_str1.replaceAll(hello, ' '); 42 | // BAD: replaceAll 43 | const str2 = old_str2.replace(/\t/g, ' '); 44 | // GOOD: replaceAll 45 | const str3 = old_str3.replace('\t', ' '); 46 | // BAD: replaceAll 47 | const mystr = mystring.replace(/bad/g, "good") 48 | ``` 49 | -------------------------------------------------------------------------------- /.grit/patterns/js/shadow_scope.grit: -------------------------------------------------------------------------------- 1 | pattern identifier_scope($name) { 2 | or { 3 | statement_block($statements) where { 4 | $statements <: some variable($declarations) where { 5 | $declarations <: contains variable_declarator(name=$name) 6 | } 7 | }, 8 | function($parameters) where { $parameters <: contains $name }, 9 | arrow_function($parameters) where { $parameters <: contains $name }, 10 | function_declaration($parameters) where { $parameters <: contains $name }, 11 | for_in_statement() as $statement where { $statement <: contains $name }, 12 | for_statement() as $statement where { $statement <: contains $name }, 13 | `try { $_ } catch($catch) { $_ }` where { $catch <: contains $name } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.grit/patterns/js/todo.grit: -------------------------------------------------------------------------------- 1 | engine marzano(0.1) 2 | language js 3 | 4 | function lines($string) { return split($string, separator=`\n`) } 5 | 6 | function todo($target, $message) { 7 | if ($message <: undefined) { 8 | $message = "This requires manual intervention." 9 | }, 10 | $lines = lines(string=$message), 11 | $lines <: some bubble($result) $x where { 12 | if ($result <: undefined) { $result = `// TODO: $x` } else { 13 | $result += `\n// $x` 14 | } 15 | }, 16 | $log_message = `TODO: $message`, 17 | log(message=$log_message, variable=$target), 18 | $lines = lines(string=$target), 19 | $lines <: some bubble($result) $x where { $result += `\n// $x` }, 20 | return $result 21 | } 22 | -------------------------------------------------------------------------------- /.grit/patterns/js/use_exponentiation_operator.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rewrite `Math.pow` ⇒ `**` 3 | tags: [ES7, SE] 4 | --- 5 | 6 | ES7 introduced the exponentiation operator `**` so that using `Math.pow` is no longer necessary. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | `Math.pow($base, $exponent)` => `($base) ** ($exponent)` 14 | ``` 15 | 16 | ## Transforms Math.pow to exponentiation operator 17 | 18 | ```javascript 19 | var a = Math.pow(0, 1); 20 | var b = Math.pow(0, b - 1); 21 | var c = Math.pow(b + 1, b - 1); 22 | var d = Math.pow(b + 1, 1); 23 | ``` 24 | 25 | ```typescript 26 | var a = (0) ** (1); 27 | var b = (0) ** (b - 1); 28 | var c = (b + 1) ** (b - 1); 29 | var d = (b + 1) ** (1); 30 | ``` 31 | -------------------------------------------------------------------------------- /.grit/patterns/js/useless_ternary_operator.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Find useless ternary operator 3 | tags: [fix] 4 | --- 5 | 6 | If $condition ? `$answer`:`$answer` then this expression returns $answer. This is probably a human error. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | js"$condition ? `$answer` : `$answer`" => js"$condition ? `$answer` : `$answer` //useless-ternary operator both result are sanme" 14 | ``` 15 | 16 | ## `<>` ⇒ `React.Fragment` 17 | 18 | ```javascript 19 | data === "value" ? `/r/${data.id}` : `/r/${data.id}` 20 | 21 | 22 | data === "value" ? `/r/${data.id}` : `/r/${data.name}` 23 | ``` 24 | 25 | ```javascript 26 | data === "value" ? `/r/${data.id}` : `/r/${data.id}` //useless-ternary operator both result are sanme 27 | 28 | 29 | data === "value" ? `/r/${data.id}` : `/r/${data.name}` 30 | ``` 31 | -------------------------------------------------------------------------------- /.grit/patterns/js/util_literal.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Literals 3 | tags: [util, syntax] 4 | --- 5 | 6 | Utility patterns for matching literals. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | pattern literal($value) { 14 | or { 15 | number() as $candidate, 16 | string(fragment=$candidate), 17 | `null` as $candidate, 18 | `undefined` as $candidate 19 | } where { $candidate <: $value } 20 | } 21 | 22 | // This is just an example to test the pattern. 23 | literal(value="93") => `42` 24 | ``` 25 | 26 | ## Matches all kinds of literals 27 | 28 | ```javascript 29 | console.log('This message is different'); 30 | console.log('93'); 31 | console.log(93); 32 | console.log(true); 33 | 34 | // Objects are not matched: 35 | console.log({ 36 | name: 'John Doe', 37 | value: 93, 38 | age: 42, 39 | }); 40 | ``` 41 | 42 | ```javascript 43 | console.log('This message is different'); 44 | console.log(42); 45 | console.log(42); 46 | console.log(true); 47 | 48 | // Objects are not matched: 49 | console.log({ 50 | name: 'John Doe', 51 | value: 42, 52 | age: 42, 53 | }); 54 | ``` 55 | -------------------------------------------------------------------------------- /.grit/patterns/js/util_upsert.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: [util, upsert, object] 3 | --- 4 | 5 | # Upsert 6 | 7 | The `upsert` pattern can be used to update a value in an object, or insert it if the key doesn't already exist. 8 | 9 | Warning: Only one `upsert` can be done per object. If you need to insert multiple keys, use `sequential` until the file converges. 10 | 11 | 12 | ```grit 13 | language js 14 | 15 | pattern upsert($key, $value) { 16 | and { or { 17 | `{ $params }` where { $params <: . } => `{ $key: $value }`, 18 | `{ $params }` where { 19 | $params <: or { 20 | some `$keylike: $old` where { 21 | $keylike <: or { 22 | $key, 23 | string(fragment=$fragment) where { 24 | $key <: r"(.+)"($raw), 25 | $fragment <: $raw 26 | } 27 | }, 28 | $old => $value 29 | }, 30 | $obj where { $obj += `, $key: $value` } 31 | } 32 | } 33 | } } 34 | } 35 | 36 | // Test case 37 | or { 38 | `hello($obj)` where { $obj <: upsert(key=`hello`, value=`'world'`) }, 39 | `string_key($obj)` where { 40 | $obj <: upsert(key=`'hello-world'`, value=`'niceness'`) 41 | } 42 | } 43 | ``` 44 | 45 | ## Simple test 46 | 47 | ```js 48 | hello({}); 49 | 50 | hello({ thing: 'two' }); 51 | 52 | hello({ hello: 'old-id' }); 53 | 54 | hello({ hello: 'old-string' }); 55 | 56 | hello({ king: 'old-string' }); 57 | ``` 58 | 59 | ```js 60 | hello({ hello: 'world' }); 61 | 62 | hello({ thing: 'two', hello: 'world' }); 63 | 64 | hello({ hello: 'world' }); 65 | 66 | hello({ hello: 'world' }); 67 | 68 | hello({ king: 'old-string', hello: 'world' }); 69 | ``` 70 | 71 | ## String key 72 | 73 | It handles cases where the key is a string. 74 | 75 | ```js 76 | string_key({}); 77 | 78 | string_key({ 'hello-world': 'boss' }); 79 | ``` 80 | 81 | ```js 82 | string_key({ 'hello-world': 'niceness' }); 83 | 84 | string_key({ 'hello-world': 'niceness' }); 85 | ``` 86 | -------------------------------------------------------------------------------- /.grit/patterns/js/variable_scoping.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: 3 | - docs 4 | - full-examples 5 | --- 6 | 7 | # Variable Scoping with `identifier_scope` 8 | 9 | Default Grit patterns are not generally aware of variable scoping, but you can use the `identifier_scope` pattern to find (or exclude) [scopes](https://developer.mozilla.org/en-US/docs/Glossary/Scope) where an identifier has been _locally_ defined. 10 | 11 | This is most often used when you want to target an import from a shared module but exclude scopes where the identifier is shadowed locally. 12 | 13 | For example, this pattern would rename `t` from the `translation` library to `translate` unless `t` is shadowed locally: 14 | 15 | ```grit 16 | language js 17 | 18 | `t` as $t => `translate` where { 19 | $t <: imported_from(from=`"translation"`), 20 | $t <: not within identifier_scope(name=`t`) 21 | } 22 | ``` 23 | 24 | Here is a simple example file where `t` is shadowed locally: 25 | 26 | ```js 27 | import { t } from 'translation'; 28 | 29 | console.log(t('hello world')); 30 | 31 | function normal() { 32 | console.log(t('hello world')); 33 | } 34 | 35 | // t is an argument to this function, so the global t is not used and we should *not* rename it here. 36 | function shadowed(t) { 37 | console.log(t('hello world')); 38 | } 39 | ``` 40 | 41 | When we rewrite it, the shadowed `t` is not renamed: 42 | 43 | ```js 44 | import { translate } from 'translation'; 45 | 46 | console.log(translate('hello world')); 47 | 48 | function normal() { 49 | console.log(translate('hello world')); 50 | } 51 | 52 | // t is an argument to this function, so the global t is not used and we should *not* rename it here. 53 | function shadowed(t) { 54 | console.log(t('hello world')); 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /.grit/patterns/js/warning_for_hardcoded_github_token.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Warning for hardcoded github token 3 | tags: [fix, warning] 4 | --- 5 | 6 | Avoid hard-coding secrets, such as credentials and sensitive data, directly into your application's source code. This practice poses a security risk as the information may be inadvertently leaked. 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language js 12 | 13 | or { 14 | `new Octokit({ auth: "$token"})`, 15 | `$name = "$token"` 16 | } where { 17 | $token <: contains r"gh[pousr]_[A-Za-z0-9_]{36,251}" => `$token // risky token` 18 | } 19 | ``` 20 | 21 | ## Warning for hardcoded github token 22 | 23 | ```javascript 24 | const b = "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" 25 | 26 | const { Octokit } = require("@octokit/rest"); 27 | 28 | const octokit = new Octokit({ 29 | auth: proccess.env.GITHUB_TOKEN, 30 | }); 31 | 32 | const octokit = new Octokit({ 33 | auth: "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", 34 | }); 35 | 36 | const octokit = new Octokit({ 37 | auth: b, 38 | }); 39 | 40 | const octokit = new Octokit({ 41 | auth: "ghp_Jreeeee", 42 | }); 43 | ``` 44 | 45 | ```javascript 46 | const b = "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 // risky token" 47 | 48 | const { Octokit } = require("@octokit/rest"); 49 | 50 | const octokit = new Octokit({ 51 | auth: proccess.env.GITHUB_TOKEN, 52 | }); 53 | 54 | const octokit = new Octokit({ 55 | auth: "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 // risky token", 56 | }); 57 | 58 | const octokit = new Octokit({ 59 | auth: b, 60 | }); 61 | 62 | const octokit = new Octokit({ 63 | auth: "ghp_Jreeeee", 64 | }); 65 | ``` 66 | -------------------------------------------------------------------------------- /.grit/patterns/js/wrap_playwright_locators.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Wrap Playwright locators 3 | tags: [hidden] 4 | --- 5 | 6 | Wrap string literal locators in Playwright-style `page.locator`. 7 | 8 | 9 | ```grit 10 | language js 11 | 12 | pattern concatenated_string() { 13 | `$a + $b` where { $a <: contains base_string(), $b <: contains base_string() } 14 | } 15 | 16 | or { 17 | concatenated_string(), 18 | base_string() as $base where $base <: not within concatenated_string() 19 | } as $bare where { 20 | or { 21 | and { $program <: contains `test.describe`, $page = `page` }, 22 | $page = `this.page` 23 | }, 24 | $bare <: not within `$page.$_($_)` , 25 | $bare => `$page.locator($bare)` 26 | } 27 | ``` 28 | 29 | ## Works in page object 30 | 31 | ```js 32 | class MyPage { 33 | get section() { 34 | return { 35 | button: 'button', 36 | input: this.page.getByLabel('my-input'), 37 | header: this.page.locator('h4'), 38 | description: '//p[contains(text(), "description")]', 39 | }; 40 | } 41 | } 42 | ``` 43 | 44 | ```js 45 | class MyPage { 46 | get section() { 47 | return { 48 | button: this.page.locator('button'), 49 | input: this.page.getByLabel('my-input'), 50 | header: this.page.locator('h4'), 51 | description: this.page.locator('//p[contains(text(), "description")]'), 52 | }; 53 | } 54 | } 55 | ``` 56 | 57 | ## Treats concatenated locators as one locator 58 | 59 | ```js 60 | class MyPage { 61 | description(value) { 62 | return '//div[contains(text(),"' + value + '")]'; 63 | } 64 | } 65 | ``` 66 | 67 | ```js 68 | class MyPage { 69 | description(value) { 70 | return this.page.locator('//div[contains(text(),"' + value + '")]'); 71 | } 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /.grit/patterns/json/dependency.grit: -------------------------------------------------------------------------------- 1 | language json 2 | 3 | pattern upgrade_dependency($target_dep, $target_version, $dependency_key) { 4 | or { 5 | `$key: $value` where { 6 | $key <: `"$target_dep"`, 7 | $value => `"$target_version"` 8 | }, 9 | pair($key, $value) where { 10 | $key <: `"$dependency_key"`, 11 | $value <: object($properties) where { 12 | $properties <: not contains pair(key=$dep_key) where { 13 | $dep_key <: contains `$target_dep` 14 | }, 15 | $properties => `"$target_dep": "$target_version",\n$properties` 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.grit/patterns/json/reverse_json_kv.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Reverse key-value pairs 3 | --- 4 | 5 | This pattern reverses key-value pairs when the value is a string. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language json 10 | 11 | `$key: $value` => `$value: $key` where { $value <: string() } 12 | ``` 13 | 14 | ## Matches a key-value pair 15 | 16 | ```json 17 | { "foo": 5, "bar": "buz" } 18 | ``` 19 | 20 | ```json 21 | { "foo": 5, "buz": "bar" } 22 | ``` 23 | -------------------------------------------------------------------------------- /.grit/patterns/markdown/delinkify.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remove links from Markdown 3 | --- 4 | 5 | This pattern replaces Markdown links with their bare text. 6 | 7 | ```grit 8 | language markdown 9 | 10 | inline_link(identifier=link_text($text)) where { 11 | $text <: contains link_text($element) 12 | } => $element 13 | ``` 14 | 15 | ## Removes a simple link 16 | 17 | ```md 18 | The root of a Grit query is a [pattern](/language/patterns). 19 | 20 | It even works with [titled links](https://www.codecademy.com/resources/docs/markdown/links "Thanks Codecademy!"). 21 | ``` 22 | 23 | ```md 24 | The root of a Grit query is a pattern. 25 | 26 | It even works with titled links. 27 | ``` 28 | -------------------------------------------------------------------------------- /.grit/patterns/python/_accesses.md: -------------------------------------------------------------------------------- 1 | # [Experimental] Variable accesses 2 | 3 | This experimental pattern helps with finding accesses to an original binding. 4 | 5 | Given an original binding `$binding` this pattern will bind to all uses of a variable which was assigned to `$binding`. 6 | 7 | ```grit 8 | language python 9 | 10 | // Define it 11 | pattern binding_access($binding) { 12 | identifier() as $identifier where { 13 | $binding <: within `$identifier = $binding` 14 | } 15 | } 16 | 17 | // Test it 18 | `{ "x": "y" }` as $original where { 19 | $program <: maybe contains binding_access(binding=$original) as $one_case where { 20 | $one_case <: within `$one_case.zoo` => `$one_case.boo` 21 | } 22 | } 23 | ``` 24 | 25 | 26 | ## Simple Example 27 | 28 | ```python 29 | config = { "x": "y" } 30 | config.zoo = 19 31 | nicely.zoo = 300 32 | ``` 33 | 34 | ```python 35 | config = { "x": "y" } 36 | config.boo = 19 37 | nicely.zoo = 300 38 | ``` 39 | -------------------------------------------------------------------------------- /.grit/patterns/python/_py_ai_rewrite.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: [ai, sample, util, hidden, example] 3 | --- 4 | 5 | # AI Rewrites in Python 6 | 7 | ```grit 8 | language python 9 | 10 | `def $_($args): 11 | $_` where { 12 | $args <: not or { 13 | [], 14 | [$_], 15 | [$_, $_] 16 | } 17 | } => ai_rewrite($match, "Convert the function args to accept a single dictionary input.") 18 | ``` 19 | 20 | ## Rewrites a function with 3 arguments 21 | 22 | Notice that this case is targeted. 23 | 24 | ```python 25 | def foo(a, b, c): 26 | print(f"Hello {a} {b}, goodbye {c}") 27 | ``` 28 | 29 | ```python 30 | def foo(input_dict): 31 | a = input_dict.get('a') 32 | b = input_dict.get('b') 33 | c = input_dict.get('c') 34 | print(f"Hello {a} {b}, goodbye {c}") 35 | ``` 36 | 37 | ## Excludes functions with 0 or 1 arguments 38 | 39 | ```python 40 | def foo(): 41 | print("Hello, world!") 42 | 43 | def bar(a): 44 | print(f"Hello {a}") 45 | ``` 46 | -------------------------------------------------------------------------------- /.grit/patterns/python/_test_add_multiple_bare_imports.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Test - add multiple bare imports 3 | --- 4 | 5 | ```grit 6 | engine marzano(0.1) 7 | language python 8 | 9 | `$_` where { 10 | $import_math = "math", 11 | $import_math <: ensure_bare_import(), 12 | $import_re = "re", 13 | $import_re <: ensure_bare_import(), 14 | $import_json = "json", 15 | $import_json <: ensure_bare_import() 16 | } 17 | ``` 18 | 19 | ## Add all imports if none is imported 20 | 21 | ```python 22 | # This is an empty block 23 | ``` 24 | 25 | ```python 26 | import math 27 | import re 28 | import json 29 | 30 | # This is an empty block 31 | ``` 32 | 33 | ## Add missing imports 34 | 35 | ```python 36 | import math 37 | import re 38 | ``` 39 | 40 | ```python 41 | import math 42 | import re 43 | import json 44 | ``` 45 | 46 | ## Add missing imports 47 | 48 | ```python 49 | import json 50 | import re 51 | ``` 52 | 53 | ```python 54 | import json 55 | import re 56 | import math 57 | ``` 58 | 59 | ## Add missing import, all in one line 60 | 61 | ```python 62 | import json, math 63 | ``` 64 | 65 | ```python 66 | import json, math 67 | import re 68 | ``` 69 | 70 | ## Don't add duplicate imports 71 | 72 | ```python 73 | import math 74 | import json 75 | import re 76 | ``` 77 | 78 | ```python 79 | import math 80 | import json 81 | import re 82 | ``` 83 | 84 | ## Don't add duplicate imports (different order) 85 | 86 | ```python 87 | import re 88 | import json 89 | import math 90 | ``` 91 | 92 | ```python 93 | import re 94 | import json 95 | import math 96 | ``` 97 | 98 | ## Don't add duplicate imports, all in one line 99 | 100 | ```python 101 | import json, math, re 102 | ``` 103 | 104 | ```python 105 | import json, math, re 106 | ``` 107 | -------------------------------------------------------------------------------- /.grit/patterns/python/_test_add_one_bare_import.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Test - add one bare import 3 | --- 4 | 5 | ```grit 6 | engine marzano(0.1) 7 | language python 8 | 9 | `$_` where { $import = "math", $import <: ensure_bare_import() } 10 | ``` 11 | 12 | ## Add one bare import 13 | 14 | 15 | ```python 16 | # Add one 17 | ``` 18 | 19 | ```python 20 | import math 21 | 22 | # Add one 23 | ``` 24 | 25 | ## Do not add duplicate bare import 26 | 27 | ```python 28 | import math 29 | ``` 30 | 31 | ```python 32 | import math 33 | ``` 34 | -------------------------------------------------------------------------------- /.grit/patterns/python/_test_conditional_imports.md: -------------------------------------------------------------------------------- 1 | In Python notebooks, it is common to sometimes have cells that conditionally import a specific import. This issue was reported [here](https://github.com/getgrit/gritql/issues/524). 2 | 3 | This is a problem when attempting to _consolidate_ imports as we might end up adding a replacement import onto the conditional import. 4 | 5 | The solution is to ignore "late" imports when considering imports to attach ourselves to. 6 | 7 | ```grit 8 | language python 9 | 10 | or { 11 | `nowhere` as $nothing where { add_import(source="nowhere", name="nothing") }, 12 | `TypedDict` as $X where { 13 | $X <: within `from typing import $_` , 14 | add_import(source="typing_extensions", name="TypedDict"), 15 | $X => . 16 | } 17 | } 18 | ``` 19 | 20 | ## Preserve the order of multiple cells 21 | 22 | ```python 23 | from typing import TypedDict 24 | 25 | def foo(x: TypedDict): 26 | pass 27 | 28 | from typing_extensions import Annotated 29 | 30 | def bar(): 31 | pass 32 | ``` 33 | 34 | Notice that we keep two separate typing_extension imports: 35 | 36 | ```python 37 | 38 | from typing_extensions import TypedDict 39 | 40 | def foo(x: TypedDict): 41 | pass 42 | 43 | from typing_extensions import Annotated 44 | 45 | def bar(): 46 | pass 47 | ``` 48 | 49 | ## But consolidate imports when there is only one cell 50 | 51 | ```python 52 | from typing import TypedDict 53 | from typing_extensions import Annotated 54 | 55 | def foo(x: TypedDict): 56 | pass 57 | ``` 58 | 59 | ```python 60 | from typing_extensions import Annotated, TypedDict 61 | 62 | def foo(x: TypedDict): 63 | pass 64 | ``` 65 | 66 | ## Insert new imports at the last safe position 67 | 68 | ```python 69 | from elsewhere import something 70 | 71 | def foo(x: TypedDict): 72 | pass 73 | 74 | # This is unsafe 75 | from danger import foobar 76 | 77 | def bar(): 78 | print(nowhere) 79 | 80 | ``` 81 | 82 | ```python 83 | from elsewhere import something 84 | from nowhere import nothing 85 | 86 | def foo(x: TypedDict): 87 | pass 88 | 89 | # This is unsafe 90 | from danger import foobar 91 | 92 | def bar(): 93 | print(nowhere) 94 | 95 | ``` 96 | -------------------------------------------------------------------------------- /.grit/patterns/python/_test_correct_source.md: -------------------------------------------------------------------------------- 1 | Test for ensuring new imports go to the right place, from [this issue](https://github.com/getgrit/gritql/issues/449). 2 | 3 | ```grit 4 | engine marzano(0.1) 5 | language python 6 | 7 | `$x = 1` where { 8 | add_import(source="typing_extensions", name="Self"), 9 | add_import(source="pydantic", name="model_validator") 10 | } 11 | ``` 12 | 13 | Input: 14 | 15 | ```python 16 | import math 17 | x = 1 18 | ``` 19 | 20 | Expected output: 21 | 22 | ```python 23 | import math 24 | from typing_extensions import Self 25 | from pydantic import model_validator 26 | 27 | x = 1 28 | ``` 29 | -------------------------------------------------------------------------------- /.grit/patterns/python/_test_ensure_import_from.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Test - ensure import from 3 | --- 4 | 5 | ```grit 6 | engine marzano(0.1) 7 | language python 8 | 9 | `$_` where { $import = "prod", $import <: ensure_import_from(source=`math`) } 10 | ``` 11 | 12 | ## Add missing import 13 | 14 | ```python 15 | # Empty block 16 | ``` 17 | 18 | ```python 19 | from math import prod 20 | 21 | # Empty block 22 | ``` 23 | 24 | ## Add one more name to source 25 | 26 | ```python 27 | from math import log 28 | ``` 29 | 30 | ```python 31 | from math import log, prod 32 | ``` 33 | 34 | ## Keep existing import 35 | 36 | ```python 37 | from math import prod 38 | ``` 39 | 40 | ```python 41 | from math import prod 42 | ``` 43 | 44 | ## Add from import even if there is a bare import 45 | 46 | ```python 47 | import math 48 | ``` 49 | 50 | ```python 51 | import math 52 | from math import prod 53 | ``` 54 | -------------------------------------------------------------------------------- /.grit/patterns/python/_test_is_bare_imported.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Test - is bare imported 3 | --- 4 | 5 | ```grit 6 | engine marzano(0.1) 7 | language python 8 | 9 | integer() as $int where { 10 | $math = "math", 11 | $re = "re", 12 | $json = "json", 13 | if ($math <: is_bare_imported()) { $has_math = "true" } else { 14 | $has_math = "false" 15 | }, 16 | if ($re <: is_bare_imported()) { $has_re = "true" } else { 17 | $has_re = "false" 18 | }, 19 | if ($json <: is_bare_imported()) { $has_json = "true" } else { 20 | $has_json = "false" 21 | } 22 | } => `$has_math, $has_re, $has_json` 23 | ``` 24 | 25 | 26 | ## All bare imports missing 27 | 28 | ```python 29 | 42 30 | ``` 31 | 32 | ```python 33 | false, false, false 34 | ``` 35 | 36 | ## Two bare imports present 37 | 38 | ```python 39 | import math 40 | import json 41 | 42 | 42 43 | ``` 44 | 45 | ```python 46 | import math 47 | import json 48 | 49 | true, false, true 50 | ``` 51 | 52 | ## Imported bare import as part of a list 53 | 54 | ```python 55 | import json, re 56 | 57 | 42 58 | ``` 59 | 60 | ```python 61 | import json, re 62 | 63 | false, true, true 64 | ``` 65 | 66 | ## From import is not a bare import 67 | 68 | ```python 69 | from math import log 70 | from re import match 71 | 72 | import json 73 | 74 | 42 75 | ``` 76 | 77 | ```python 78 | from math import log 79 | from re import match 80 | 81 | import json 82 | 83 | false, false, true 84 | ``` 85 | -------------------------------------------------------------------------------- /.grit/patterns/python/_test_two_imports.md: -------------------------------------------------------------------------------- 1 | Test for adding multiple imports to the same library, for [this issue](https://github.com/getgrit/gritql/issues/450). 2 | 3 | ```grit 4 | engine marzano(0.1) 5 | language python 6 | 7 | `x = 1` as $SELF where { 8 | add_import(source="pydantic", name="Self"), 9 | add_import(source="pydantic", name="pydantic1") 10 | } 11 | ``` 12 | 13 | Input: 14 | 15 | ```python 16 | from pydantic import BaseModel, Extra, Field, root_validator 17 | 18 | x = 1 19 | ``` 20 | 21 | Expected Output: 22 | 23 | ```python 24 | from pydantic import BaseModel, Extra, Field, root_validator, Self, pydantic1 25 | 26 | x = 1 27 | ``` 28 | -------------------------------------------------------------------------------- /.grit/patterns/python/any_to_in.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Convert Any to In 3 | --- 4 | 5 | Converts `any()` functions to simpler `in` statements. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language python 10 | 11 | `any($x == $val for $x in $arr)` => `$val in $arr` 12 | ``` 13 | 14 | ## Convert any to in 15 | 16 | ```python 17 | if any(hat == "bowler" for hat in hats): 18 | shout("I have a bowler hat!") 19 | 20 | if any(hat > "bowler" for hat in hats): 21 | shout("I have a bowler hat!") 22 | ``` 23 | 24 | ```python 25 | if "bowler" in hats: 26 | shout("I have a bowler hat!") 27 | 28 | if any(hat > "bowler" for hat in hats): 29 | shout("I have a bowler hat!") 30 | ``` 31 | -------------------------------------------------------------------------------- /.grit/patterns/python/aware_utc.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Prefer timezone-aware datetimes to utcnow() 3 | --- 4 | 5 | To get the current time in UTC use a datetime object with the timezone explicitly set to UTC. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language python 10 | 11 | and { $new_import = `timezone`, 12 | `datetime.utcnow()` => `datetime.now($new_import.utc)` where { 13 | $new_import <: ensure_import_from(source=`datetime`) 14 | } } 15 | ``` 16 | 17 | ## Aware date-time for UTC 18 | 19 | ```python 20 | from datetime import datetime 21 | 22 | this_moment_utc = datetime.utcnow() 23 | ``` 24 | 25 | ```python 26 | from datetime import datetime, timezone 27 | 28 | this_moment_utc = datetime.now(timezone.utc) 29 | ``` 30 | -------------------------------------------------------------------------------- /.grit/patterns/python/binary_op_ident.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Binary Operation Identity 3 | --- 4 | 5 | Some binary operations can be simplified into constants, this lint performs those simplifications. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language python 10 | 11 | or { 12 | `$var | $var` => `$var`, 13 | `$var & $var` => `$var`, 14 | `$var ^ $var` => `0`, 15 | `$var - $var` => `0`, 16 | `$var % $var` => `0`, 17 | `$var / $var` => `1`, 18 | `$var // $var` => `1` 19 | } 20 | ``` 21 | 22 | ## Binary operation identity 23 | 24 | ```python 25 | x | x 26 | x & x 27 | x ^ x 28 | x - x 29 | x / x 30 | x // x 31 | x % x 32 | 33 | x + x 34 | x * x 35 | ``` 36 | 37 | ```python 38 | x 39 | x 40 | 0 41 | 0 42 | 1 43 | 1 44 | 0 45 | 46 | x + x 47 | x * x 48 | ``` 49 | -------------------------------------------------------------------------------- /.grit/patterns/python/bool_in_expr_ident.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Boolean If Expression Identity 3 | --- 4 | 5 | When a boolean expression is used in an if-else to get a boolean value, use the boolean value directly. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language python 10 | 11 | // IMPROVEMENT: Could be more intelligent here and figure out if the expression is 12 | // boolean in itself and therefore does not need the bool() wrapper 13 | or { 14 | `True if $expr else False` => `bool($expr)`, 15 | `False if $expr else True` => `not bool($expr)` 16 | } 17 | ``` 18 | 19 | ## Boolean if expression identity 20 | 21 | ```python 22 | some_var = True if some_boolean_expression else False 23 | some_var = False if some_boolean_expression else True 24 | 25 | some_var = 1 if some_boolean_expression else 0 26 | ``` 27 | 28 | ```python 29 | some_var = bool(some_boolean_expression) 30 | some_var = not bool(some_boolean_expression) 31 | 32 | some_var = 1 if some_boolean_expression else 0 33 | ``` 34 | -------------------------------------------------------------------------------- /.grit/patterns/python/collection_builtin.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Collection Builtin To Comprehension 3 | --- 4 | 5 | Use list, set or dictionary comprehensions directly instead of calling `list()`, `dict()` or `set()`. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language python 10 | 11 | or { 12 | `list($expr for $x in $arr)` => `[$expr for $x in $arr]`, 13 | `set($expr for $x in $arr)` => `{$expr for $x in $arr}`, 14 | `dict(($key, $expr) for $x in $arr)` => `{$key: $expr for $x in $arr}` 15 | } 16 | ``` 17 | 18 | ## Collection builtin to comprehension 19 | 20 | ```python 21 | squares = list(x * x for x in y) 22 | squares = set(x * x for x in y) 23 | squares = dict((x, x * x) for x in xs) 24 | 25 | squares = any(x * x for x in y) 26 | ``` 27 | 28 | ```python 29 | squares = [x * x for x in y] 30 | squares = {x * x for x in y} 31 | squares = {x: x * x for x in xs} 32 | 33 | squares = any(x * x for x in y) 34 | ``` 35 | -------------------------------------------------------------------------------- /.grit/patterns/python/collection_to_bool.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Collection To Bool 3 | --- 4 | 5 | Replace constant collection with boolean in boolean contexts. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language python 10 | 11 | or { 12 | `if $cond: $_`, 13 | elif_clause(condition=$cond), 14 | `while $cond: $_` 15 | } where $cond <: or { 16 | `[$elms]` as $arr where { 17 | $elms <: or { 18 | [$_, ...] where $arr => `True`, 19 | . where $arr => `False` 20 | } 21 | }, 22 | `{}` => `False`, 23 | `{$_}` => `True`, 24 | r"\{.*:.*(?:,.*:.*)*\}" => `True`, 25 | r"\(\)" => `False`, 26 | r"\(.+\)" => `True` 27 | } 28 | ``` 29 | 30 | ## Collection to bool 31 | 32 | ```python 33 | if ["foo", "boo"]: 34 | baz() 35 | if ["foo"]: 36 | baz() 37 | if []: 38 | baz() 39 | if {}: 40 | baz() 41 | if {1: 1, 2: 2, 3: 3}: 42 | baz() 43 | if {1, 2, 3}: 44 | baz() 45 | if (1, 2, 3): 46 | baz() 47 | if (): 48 | baz() 49 | 50 | if "foo": 51 | baz() 52 | if [x for x in y]: 53 | baz() 54 | ``` 55 | 56 | ```python 57 | if True: 58 | baz() 59 | if True: 60 | baz() 61 | if False: 62 | baz() 63 | if False: 64 | baz() 65 | if True: 66 | baz() 67 | if True: 68 | baz() 69 | if True: 70 | baz() 71 | if False: 72 | baz() 73 | 74 | if "foo": 75 | baz() 76 | if [x for x in y]: 77 | baz() 78 | ``` 79 | -------------------------------------------------------------------------------- /.grit/patterns/python/combined_bound_check.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Combine upper and lower bound checks into a single check 3 | --- 4 | 5 | Replaces 2 individual bound checks with a single combined bound check. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language python 10 | 11 | or { 12 | `$c1 and $c2`, 13 | `$c2 and $c1` 14 | } as $all where { 15 | $upper_strict = "", 16 | $lower_strict = "", 17 | $c1 <: or { 18 | or { 19 | `$x < $upper`, 20 | `$upper > $x` 21 | }, 22 | or { 23 | `$x <= $upper`, 24 | `$upper >= $x` 25 | } where { $upper_strict = "=" } 26 | }, 27 | $c2 <: or { 28 | or { 29 | `$x > $lower`, 30 | `$lower < $x` 31 | }, 32 | or { 33 | `$x >= $lower`, 34 | `$lower <= $x` 35 | } where { $lower_strict = "=" } 36 | }, 37 | $all => `$lower <$lower_strict $x <$upper_strict $upper` 38 | } 39 | ``` 40 | 41 | ## Two sided bound checks 42 | 43 | ```python 44 | if x < 10 and x > 5: 45 | return "Ok" 46 | elif 100 >= my_var and my_var > -5: 47 | return None 48 | else: 49 | return (x < get_strict_max() and get_min() <= x) 50 | ``` 51 | 52 | ```python 53 | if 5 < x < 10: 54 | return "Ok" 55 | elif -5 < my_var <= 100: 56 | return None 57 | else: 58 | return (get_min() <= x < get_strict_max()) 59 | ``` 60 | -------------------------------------------------------------------------------- /.grit/patterns/python/comp_to_generator.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Comprehension To Generator 3 | --- 4 | 5 | Replace unneeded list comprehensions with direct generators. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language python 10 | 11 | // Helper to check for functions that accept generators 12 | pattern accept_generator() { 13 | or { 14 | `all`, 15 | `any`, 16 | `enumerate`, 17 | `frozenset`, 18 | `list`, 19 | `max`, 20 | `min`, 21 | `set`, 22 | `sum`, 23 | `tuple` 24 | } 25 | } 26 | 27 | `$func([$expr for $x in $arr])` => `$func($expr for $x in $arr)` where { 28 | $func <: accept_generator() 29 | } 30 | ``` 31 | 32 | ## Comprehension to generator 33 | 34 | ```python 35 | hat_found = any([is_hat(item) for item in wardrobe]) 36 | hat_found = list([is_hat(item) for item in wardrobe]) 37 | 38 | hat_found = dict([(item, is_hat(item)) for item in wardrobe]) 39 | ``` 40 | 41 | ```python 42 | hat_found = any(is_hat(item) for item in wardrobe) 43 | hat_found = list(is_hat(item) for item in wardrobe) 44 | 45 | hat_found = dict([(item, is_hat(item)) for item in wardrobe]) 46 | ``` 47 | -------------------------------------------------------------------------------- /.grit/patterns/python/del_comprehension.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Use comprehensions for deleting from dictionaries 3 | --- 4 | 5 | Replaces cases where deletions are made via `for` loops with comprehensions. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language python 10 | 11 | ` 12 | for $key in $dict.copy(): 13 | if $key not in $collection: 14 | del $dict[$key] 15 | ` => `$dict = {$key: value for $key, value in $dict.items() if $key in $collection}` 16 | ``` 17 | 18 | ## Delete comprehension 19 | 20 | ```python 21 | x1 = {"a": 1, "b": 2, "c": 3} 22 | for key in x1.copy(): # can't iterate over a variable that changes size 23 | if key not in x0: 24 | del x1[key] 25 | 26 | for key in x0: 27 | del x1[key] 28 | ``` 29 | 30 | ```python 31 | x1 = {"a": 1, "b": 2, "c": 3} 32 | x1 = {key: value for key, value in x1.items() if key in x0} 33 | 34 | for key in x0: 35 | del x1[key] 36 | ``` 37 | -------------------------------------------------------------------------------- /.grit/patterns/python/dict_comprehension.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Dictionary Comprehension 3 | --- 4 | 5 | Replaces dictionaries created with `for` loops with dictionary comprehensions. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language python 10 | 11 | for_statement(body=block(statements=[ 12 | `$var[$key_expr] = $expr` 13 | ]), left=$key, right=$iter) as $assign where { 14 | $assign <: after `$var = {}` => ., 15 | $assign => `$var = {$key_expr: $expr for $key in $iter}` 16 | } 17 | ``` 18 | 19 | ## Dictionary comprehension 20 | 21 | ```python 22 | cubes = {} 23 | for i in range(100): 24 | cubes[i] = i**3 25 | cubes = {} 26 | for i in range(100): 27 | cubes[i*2] = i**3 28 | cubes = {1: 2} 29 | for i in range(100): 30 | cubes[i*2] = i**3 31 | ``` 32 | 33 | ```python 34 | 35 | cubes = {i: i**3 for i in range(100)} 36 | cubes = {i*2: i**3 for i in range(100)} 37 | cubes = {1: 2} 38 | for i in range(100): 39 | cubes[i*2] = i**3 40 | ``` 41 | -------------------------------------------------------------------------------- /.grit/patterns/python/dict_get_instead_of_if_else_block.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Use dict.get with default instead of if-else 3 | --- 4 | 5 | Join multiple with statements into a single one. Rule [SIM401](https://github.com/MartinThoma/flake8-simplify/issues/72) from [flake8-simplify](https://github.com/MartinThoma/flake8-simplify). 6 | 7 | Caveat: the transformation is not run if either `$key` or `$default` have a function call, 8 | as they would be called a different number of times in the new code. 9 | 10 | ```grit 11 | engine marzano(0.1) 12 | language python 13 | 14 | ` 15 | if $key in $dict: 16 | $var = $dict[$key] 17 | else: 18 | $var = $default 19 | ` => `$var = $dict.get($key, $default)` where { 20 | ! $key <: contains call(), 21 | ! $default <: contains call() 22 | } 23 | ``` 24 | 25 | ## Replace if-else with dict.get() 26 | 27 | ```python 28 | if "my_key" in example_dict: 29 | thing = example_dict["my_key"] 30 | else: 31 | thing = "default_value" 32 | 33 | 34 | # Left as is 35 | 36 | if f() in example_dict: 37 | thing = example_dict[f()] 38 | else: 39 | thing = "default_value" 40 | 41 | if "my_key" in example_dict: 42 | thing = example_dict["my_key"] 43 | else: 44 | thing = "default_value" + f() 45 | 46 | if "name" in d: 47 | name = d[name] 48 | else: 49 | name = "foo" 50 | 51 | if "name" in d: 52 | name = d["name"] 53 | else: 54 | surname = "foo" 55 | ``` 56 | 57 | ```python 58 | thing = example_dict.get("my_key", "default_value") 59 | 60 | 61 | # Left as is 62 | 63 | if f() in example_dict: 64 | thing = example_dict[f()] 65 | else: 66 | thing = "default_value" 67 | 68 | if "my_key" in example_dict: 69 | thing = example_dict["my_key"] 70 | else: 71 | thing = "default_value" + f() 72 | 73 | if "name" in d: 74 | name = d[name] 75 | else: 76 | name = "foo" 77 | 78 | if "name" in d: 79 | name = d["name"] 80 | else: 81 | surname = "foo" 82 | ``` 83 | -------------------------------------------------------------------------------- /.grit/patterns/python/in_dict_keys.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: In dict instead of in dict.keys() 3 | --- 4 | 5 | Rewrite `in dict.keys()` to `in dict`. Rule [SIM118](https://github.com/MartinThoma/flake8-simplify/issues/40) from [flake8-simplify](https://github.com/MartinThoma/flake8-simplify). 6 | 7 | Limitations: 8 | - The change is not applied to for loops. 9 | 10 | 11 | ```grit 12 | engine marzano(0.1) 13 | language python 14 | 15 | `$var in $dict.keys()` => `$var in $dict` 16 | ``` 17 | 18 | ## Replace `in dict.keys()` with `in dict` 19 | 20 | ```python 21 | found = key in foo.keys() 22 | 23 | if name in names.keys(): 24 | print(f"{name} found") 25 | 26 | # TODO: this for loop should also be simplified 27 | for name in names.keys(): 28 | print(name) 29 | 30 | key in sorted(foo.keys())[:10] 31 | 32 | (a, b) in foo.items() 33 | ``` 34 | 35 | ```python 36 | found = key in foo 37 | 38 | if name in names: 39 | print(f"{name} found") 40 | 41 | # TODO: this for loop should also be simplified 42 | for name in names.keys(): 43 | print(name) 44 | 45 | key in sorted(foo.keys())[:10] 46 | 47 | (a, b) in foo.items() 48 | ``` 49 | -------------------------------------------------------------------------------- /.grit/patterns/python/insecure_hash_function.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Detected use of an insecure `MD4` or `MD5` hash function and replace with `SHA256` 3 | tags: [fix, security] 4 | --- 5 | 6 | Identified the utilization of an insecure `MD4` or `MD5` hash function, both of which have well-documented vulnerabilities and are deemed deprecated. It is recommended to replace them with more secure options such as `SHA256` or a comparable hash function for improved security. 7 | 8 | ### references 9 | 10 | - [rfc6151](https://tools.ietf.org/html/rfc6151) 11 | - [stackexchange](https://crypto.stackexchange.com/questions/44151/how-does-the-flame-malware-take-advantage-of-md5-collision) 12 | - [sha3_256](https://pycryptodome.readthedocs.io/en/latest/src/hash/sha3_256.html) 13 | 14 | ```grit 15 | engine marzano(0.1) 16 | language python 17 | 18 | `hashlib.new($params)` where { 19 | or { 20 | $params <: contains `'md5'` => `'sha256'`, 21 | $params <: contains `'MD5'` => `'sha256'`, 22 | $params <: contains `'md4'` => `'sha256'`, 23 | $params <: contains `'MD4'` => `'sha256'` 24 | } 25 | } 26 | ``` 27 | 28 | ## Detected use of an insecure `MD4` or `MD5` hash function 29 | 30 | ### BAD: insecure-hash-function 31 | 32 | ```python 33 | import hashlib 34 | 35 | hashlib.new("md5") 36 | 37 | hashlib.new('md4', 'test') 38 | 39 | hashlib.new(name='md5', string='test') 40 | 41 | hashlib.new('MD4', string='test') 42 | 43 | hashlib.new(string='test', name='MD5') 44 | ``` 45 | 46 | ```python 47 | import hashlib 48 | 49 | hashlib.new('sha256') 50 | 51 | hashlib.new('sha256', 'test') 52 | 53 | hashlib.new(name='sha256', string='test') 54 | 55 | hashlib.new('sha256', string='test') 56 | 57 | hashlib.new(string='test', name='sha256') 58 | ``` 59 | 60 | ### GOOD: secure-hash-function 61 | 62 | ```python 63 | hashlib.new('sha256') 64 | 65 | hashlib.new('SHA512') 66 | ``` 67 | -------------------------------------------------------------------------------- /.grit/patterns/python/join_nested_withs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Join nested with statements 3 | --- 4 | 5 | Join nested with statements into a single one. Rule [SIM117](https://github.com/MartinThoma/flake8-simplify/issues/35) from [flake8-simplify](https://github.com/MartinThoma/flake8-simplify). 6 | 7 | Limitations: 8 | 9 | - Only two nested with statements are joined. 10 | 11 | ```grit 12 | engine marzano(0.1) 13 | language python 14 | 15 | ` 16 | with $clause1: 17 | with $clause2: 18 | $with_body 19 | ` => `with $clause1, $clause2: 20 | $with_body` 21 | ``` 22 | 23 | ## Join with statements 24 | 25 | ```python 26 | with open("file1.txt") as f1: 27 | with open("file2.txt", "r+") as f2: 28 | pass 29 | 30 | with A() as a, B() as b: 31 | with C() as c: 32 | pass 33 | 34 | with A() as a: 35 | with B() as b, C() as c: 36 | pass 37 | 38 | # TODO: should be joined into a single with 39 | with A() as a: 40 | with B() as b: 41 | with C() as c: 42 | pass 43 | ``` 44 | 45 | ```python 46 | with open("file1.txt") as f1, open("file2.txt", "r+") as f2: 47 | pass 48 | 49 | with A() as a, B() as b, C() as c: 50 | pass 51 | 52 | with A() as a, B() as b, C() as c: 53 | pass 54 | 55 | # TODO: should be joined into a single with 56 | with A() as a, B() as b: 57 | with C() as c: 58 | pass 59 | ``` 60 | -------------------------------------------------------------------------------- /.grit/patterns/python/json_response_vs_json.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: use `JsonResponse` over `json` and `HttpResponse` 3 | tags: [fix, best-practice, django] 4 | --- 5 | 6 | `JsonResponse` in Django offers a concise and efficient way to return `JSON` responses compared to using `json.dumps` along with `HttpResponse`. It simplifies the process by automatically handling serialization and setting the correct content type. 7 | 8 | ```grit 9 | engine marzano(0.1) 10 | language python 11 | 12 | `def $funcName($params): $funcBody` where { 13 | $import = "JsonResponse", 14 | $import <: ensure_import_from(source=`django.http`), 15 | $funcBody <: contains `$json_data = json.dumps($data)` => ., 16 | $funcBody <: contains `return HttpResponse($json_data, content_type='application/json')` => `return JsonResponse($data)` 17 | } 18 | ``` 19 | 20 | ## with `json` and `HttpResponse` 21 | 22 | ```python 23 | import json 24 | from django.http import HttpResponse 25 | 26 | def my_view(request): 27 | data = {'foo': 'bar'} 28 | json_data = json.dumps(data) 29 | return HttpResponse(json_data, content_type='application/json') 30 | ``` 31 | 32 | ```python 33 | import json 34 | from django.http import HttpResponse, JsonResponse 35 | 36 | def my_view(request): 37 | data = {'foo': 'bar'} 38 | return JsonResponse(data) 39 | ``` 40 | 41 | ## without `json` and `HttpResponse` 42 | 43 | ```python 44 | from django.http import JsonResponse 45 | 46 | def my_view(request): 47 | data = {'foo': 'bar'} 48 | return JsonResponse(data) 49 | ``` 50 | -------------------------------------------------------------------------------- /.grit/patterns/python/math_prod.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Use `math.prod` instead of multiplying in a loop 3 | --- 4 | 5 | This pattern transforms a loop that computes the product of a list of numbers into a call to `math.prod` (introduced in Python 3.8). 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language python 10 | 11 | pattern prod_init($accum) { 12 | or { 13 | `$accum = 1`, 14 | `$accum = 1.0` 15 | } 16 | } 17 | 18 | pattern prod_accum($accum, $factor) { 19 | or { 20 | `$accum *= $factor`, 21 | `$accum = $accum * $factor`, 22 | `$accum = $factor * $accum` 23 | } 24 | } 25 | 26 | for_statement(body=block(statements=[ 27 | prod_accum(accum=$var, factor=$left) 28 | ]), $left, $right) as $for where { 29 | $for <: after prod_init(accum=$var) => ., 30 | $left <: identifier(), 31 | $import = `math`, 32 | $import <: ensure_bare_import() 33 | } => `math.prod($right)` 34 | ``` 35 | 36 | ## Transforms for loop to `math.prod` 37 | 38 | ```python 39 | from math import log 40 | 41 | n = 1 42 | for x in range(10): 43 | n *= x 44 | 45 | n = 1.0 46 | for x in range(10): 47 | n = n * x 48 | 49 | n = 1 50 | for x in range(10): 51 | n = x * n 52 | 53 | prod = 1 54 | for x in [4, 5, 6]: 55 | prod *= x 56 | 57 | # Left as is 58 | 59 | n = 1 60 | for x in range(10): 61 | y = n * x 62 | 63 | n = 1 64 | for x in range(10): 65 | n = y * x 66 | 67 | n = 1 68 | for x in range(10): 69 | n *= x 70 | print("multiplied") 71 | ``` 72 | 73 | ```python 74 | from math import log 75 | import math 76 | 77 | 78 | math.prod(range(10)) 79 | 80 | math.prod(range(10)) 81 | 82 | math.prod(range(10)) 83 | 84 | math.prod([4, 5, 6]) 85 | 86 | # Left as is 87 | 88 | n = 1 89 | for x in range(10): 90 | y = n * x 91 | 92 | n = 1 93 | for x in range(10): 94 | n = y * x 95 | 96 | n = 1 97 | for x in range(10): 98 | n *= x 99 | print("multiplied") 100 | ``` 101 | -------------------------------------------------------------------------------- /.grit/patterns/python/no_null_string_field.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Avoid using `null` on string-based fields such as CharField and TextField 3 | tags: [fix, best-practice, Django] 4 | --- 5 | 6 | Avoid using null on string-based fields such as `CharField` and `TextField`. If a string-based field has `null=True`, that means it has two possible values for `no data`: `NULL`, and the empty string. In most cases, it's redundant to have two possible values for "no data" the Django convention is to use the empty string, not `NULL`. 7 | 8 | ```grit 9 | engine marzano(0.1) 10 | language python 11 | 12 | or { 13 | `$models.CharField($params)`, 14 | `$models.TextField($params)` 15 | } where { 16 | or { 17 | and { 18 | $params <: contains `null=True` => ., 19 | $params <: contains `blank=True` 20 | }, 21 | $params <: contains `null=True` => `blank=True` 22 | } 23 | } 24 | ``` 25 | 26 | ## Model with `null=True` 27 | 28 | ```python 29 | from django.db import models 30 | from django.db.models import Model 31 | 32 | class FakeModel(Model): 33 | fieldOne = models.CharField( 34 | max_length=200, 35 | null=True) 36 | ``` 37 | 38 | ```python 39 | from django.db import models 40 | from django.db.models import Model 41 | 42 | class FakeModel(Model): 43 | fieldOne = models.CharField( 44 | max_length=200, 45 | blank=True) 46 | ``` 47 | 48 | ## Model with `null=True` and `blank=True` 49 | 50 | ```python 51 | fieldThree = models.CharField( 52 | unique=True, 53 | null=True, 54 | blank=True, 55 | max_length=100 56 | ) 57 | ``` 58 | 59 | ```python 60 | fieldThree = models.CharField( 61 | unique=True, 62 | 63 | blank=True, 64 | max_length=100 65 | ) 66 | ``` 67 | 68 | ## Model without `null=True` and `blank=True` 69 | 70 | ```python 71 | notText = models.IntegerField( 72 | max_value=255 73 | ) 74 | ``` 75 | -------------------------------------------------------------------------------- /.grit/patterns/python/no_skipped_tests.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: [pytest, testing, hygiene] 3 | --- 4 | 5 | # No skipped tests 6 | 7 | Disable skipping pytest tests without an explanation. 8 | 9 | 10 | ```grit 11 | engine marzano(0.1) 12 | language python 13 | 14 | decorated_definition($decorators, $definition) where { 15 | $decorators <: contains `@pytest.mark.skip($info)` => . where { 16 | $info <: not includes `reason` 17 | } 18 | } 19 | ``` 20 | 21 | ## Forbidden 22 | 23 | ```py 24 | @pytest.mark.skip() 25 | def test_the_unknown(): 26 | pass 27 | ``` 28 | 29 | ```py 30 | def test_the_unknown(): 31 | pass 32 | ``` 33 | 34 | ## Reason explanation 35 | 36 | If you include a reason explaining why the test is skipped, it will be allowed. 37 | 38 | ```py 39 | @pytest.mark.skip(reason="no way of currently testing this") 40 | def test_the_unknown(): 41 | pass 42 | ``` 43 | 44 | ## Other decorators 45 | 46 | Any other decorators are still preserved. 47 | 48 | ```py 49 | @pytest.mark.skip() 50 | @pytest.mark.xfail(reason="This test is expected to fail") 51 | def test_the_unknown(): 52 | pass 53 | ``` 54 | 55 | ```py 56 | @pytest.mark.xfail(reason="This test is expected to fail") 57 | def test_the_unknown(): 58 | pass 59 | ``` 60 | 61 | 62 | ## Ordering doesn't matter 63 | 64 | ```py 65 | @pytest.mark.xfail(reason="This test is expected to fail") 66 | @pytest.mark.skip() 67 | def test_the_unknown(): 68 | pass 69 | ``` 70 | 71 | ```py 72 | @pytest.mark.xfail(reason="This test is expected to fail") 73 | def test_the_unknown(): 74 | pass 75 | ``` 76 | -------------------------------------------------------------------------------- /.grit/patterns/python/openai_global.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Upgrade to OpenAI Python SDK - Global Client 3 | tags: [python, openai, migration] 4 | --- 5 | 6 | Convert OpenAI from openai version to the v1 version, while continuing to use the global client. This is a variant of the [client-based version](https://github.com/getgrit/python/blob/main/.grit/patterns/openai.md). 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language python 12 | 13 | file($body) where { $body <: openai_main(client=`openai`) } 14 | ``` 15 | 16 | ## Rewrite completions 17 | 18 | ```python 19 | import openai 20 | 21 | completion = await openai.Completion.acreate(model="davinci-002", prompt="Hello world") 22 | chat_completion = await openai.ChatCompletion.acreate(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello world"}]) 23 | ``` 24 | 25 | ```python 26 | import openai 27 | 28 | completion = await openai.completions.create(model="davinci-002", prompt="Hello world") 29 | chat_completion = await openai.chat.completions.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello world"}]) 30 | ``` 31 | 32 | ## Global settings 33 | 34 | If you use the global client, options can be set on itself. 35 | 36 | ```python 37 | import openai 38 | 39 | if openai_proxy: 40 | openai.proxy = openai_proxy 41 | openai.api_base = self.openai_api_base 42 | ``` 43 | 44 | ```python 45 | import openai 46 | 47 | if openai_proxy: 48 | openai.proxy = openai_proxy 49 | openai.api_base = self.openai_api_base 50 | ``` 51 | 52 | ## Remap errors 53 | 54 | ```python 55 | import openai 56 | 57 | try: 58 | completion = openai.Completion.create(model="davinci-002", prompt="Hello world") 59 | chat_completion = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello world"}]) 60 | except openai.error.RateLimitError as err: 61 | pass 62 | ``` 63 | 64 | ```python 65 | import openai 66 | 67 | try: 68 | completion = openai.completions.create(model="davinci-002", prompt="Hello world") 69 | chat_completion = openai.chat.completions.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello world"}]) 70 | except openai.RateLimitError as err: 71 | pass 72 | ``` 73 | -------------------------------------------------------------------------------- /.grit/patterns/python/print_to_log.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Print to log 3 | --- 4 | 5 | Rewrite `print` statements using `log`. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language python 10 | 11 | `print($x)` => `log($x)` 12 | ``` 13 | 14 | ## Transforms a log statement 15 | 16 | ```python 17 | print("hello world!") 18 | log("this is python") 19 | ``` 20 | 21 | ```python 22 | log("hello world!") 23 | log("this is python") 24 | ``` 25 | -------------------------------------------------------------------------------- /.grit/patterns/python/py_marzano.grit: -------------------------------------------------------------------------------- 1 | engine marzano(0.1) 2 | language python 3 | 4 | // All core stdlib functions can be done here 5 | pattern before_each_file_stdlib() { before_each_file_prep_imports() } 6 | 7 | pattern after_each_file_stdlib() { 8 | any { 9 | after_each_file_handle_imports(), 10 | after_each_file_global_rewrites() 11 | } 12 | } 13 | 14 | // These could be redefined in the future (not presently supported) 15 | pattern before_each_file() { before_each_file_stdlib() } 16 | 17 | pattern after_each_file() { after_each_file_stdlib() } 18 | -------------------------------------------------------------------------------- /.grit/patterns/python/py_no_debugger.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remove debugger 3 | tags: [fix, good-practice] 4 | --- 5 | 6 | We should remove debugger from production code 7 | 8 | 9 | ```grit 10 | engine marzano(0.1) 11 | language python 12 | 13 | or { 14 | `import $pdb as $db`, 15 | `import pdb` where $db = `pdb` 16 | } where { 17 | $program <: maybe contains or { 18 | `$db.set_trace($_)` => ., 19 | `$db.Pdb.set_trace($_)` => ., 20 | `$pdb.Pdb.set_trace($_)` => . 21 | } 22 | } 23 | ``` 24 | 25 | ## Remove debugger direct as import 26 | 27 | ```python 28 | import pdb as db 29 | 30 | 31 | def foo(): 32 | # BAD: pdb-remove 33 | db.set_trace() 34 | 35 | a = "apple" 36 | 37 | db = "the string, not the library" 38 | #ok:pdb-remove 39 | pdb = "also a string" 40 | # BAD: pdb-remove 41 | pdb.Pdb.set_trace() 42 | # BAD: pdb-remove 43 | db.Pdb.set_trace(...) 44 | ``` 45 | 46 | ```python 47 | import pdb as db 48 | 49 | 50 | def foo(): 51 | # BAD: pdb-remove 52 | 53 | a = "apple" 54 | 55 | db = "the string, not the library" 56 | #ok:pdb-remove 57 | pdb = "also a string" 58 | # BAD: pdb-remove 59 | # BAD: pdb-remove 60 | 61 | ``` 62 | 63 | ## Remove debugger direct import 64 | 65 | ```python 66 | # BAD: python-debugger-found 67 | import pdb 68 | 69 | # BAD: python-debugger-found 70 | pdb.set_trace() 71 | 72 | 73 | def foo(): 74 | # GOOD: python-debugger-found 75 | p = not_pdb.set_trace() 76 | ``` 77 | 78 | ```python 79 | # BAD: python-debugger-found 80 | import pdb 81 | 82 | # BAD: python-debugger-found 83 | 84 | 85 | def foo(): 86 | # GOOD: python-debugger-found 87 | p = not_pdb.set_trace() 88 | ``` 89 | -------------------------------------------------------------------------------- /.grit/patterns/python/py_todo.grit: -------------------------------------------------------------------------------- 1 | engine marzano(0.1) 2 | language python 3 | 4 | function lines($string) { return split($string, separator=`\n`) } 5 | 6 | function todo($target, $message) { 7 | if ($message <: undefined) { 8 | $message = "This requires manual intervention." 9 | }, 10 | $lines = lines(string=$message), 11 | $result = [], 12 | $lines <: some bubble($result) $x where { 13 | if ($result <: []) { $result += `# TODO: $x` } else { $result += `# $x` } 14 | }, 15 | $log_message = `TODO: $message`, 16 | log(message=$log_message, variable=$target), 17 | $lines = lines(string=$target), 18 | $lines <: some bubble($result) $x where { $result += `# $x` }, 19 | $result = join(list=$result, separator=`\n`), 20 | return $result 21 | } 22 | -------------------------------------------------------------------------------- /.grit/patterns/python/replace_tempfile.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Replace `tempfile.mktemp` ⇒ `tempfile.NamedTemporaryFile` 3 | tags: [fix, good-practice] 4 | --- 5 | 6 | Prefer using tempfile.NamedTemporaryFile instead. According to the official Python documentation, the tempfile.mktemp function is considered unsafe and should be avoided. This is because the generated file name may initially point to a non-existent file, and by the time you attempt to create it, another process may have already created a file with the same name, leading to potential conflicts. 7 | 8 | - [reference](https://docs.python.org/3/library/tempfile.html#tempfile.mkdtemp) 9 | 10 | 11 | ```grit 12 | engine marzano(0.1) 13 | language python 14 | 15 | `$tempfile.mktemp($params)` => `$tempfile.NamedTemporaryFile($[params]delete=False)` where { 16 | // If $params is present, add the comma 17 | if ($params <: not .) { $params => `$params, ` } 18 | } 19 | ``` 20 | 21 | ## Replace `tempfile.mktemp` ⇒ `tempfile.NamedTemporaryFile` 22 | 23 | ```python 24 | import tempfile as tf 25 | 26 | # BAD: tempfile-insecure 27 | x = tempfile.mktemp() 28 | 29 | # BAD: tempfile-insecure 30 | x = tempfile.mktemp(dir="/tmp") 31 | ``` 32 | 33 | ```python 34 | import tempfile as tf 35 | 36 | # BAD: tempfile-insecure 37 | x = tempfile.NamedTemporaryFile(delete=False) 38 | 39 | # BAD: tempfile-insecure 40 | x = tempfile.NamedTemporaryFile(dir="/tmp", delete=False) 41 | ``` 42 | -------------------------------------------------------------------------------- /.grit/patterns/python/ternary_op.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Replace if-else with ternary operation where appropriate 3 | --- 4 | 5 | Replaces an assignment to the same variable done across an if-else with a ternary operator when both are equivalent. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language python 10 | 11 | ` 12 | if $cond: 13 | $if_body 14 | else: 15 | $else_body 16 | ` as $cond_body where { 17 | $if_body <: block(statements=[`$var = $if_value`]), 18 | $else_body <: block(statements=[`$var = $else_value`]), 19 | $cond_body => `$var = $if_value if $cond else $else_value` 20 | } 21 | ``` 22 | 23 | ## Replace assign across if-else with ternary operator 24 | 25 | ```python 26 | if condition: 27 | x = 1 28 | else: 29 | x = 2 30 | 31 | if condition: 32 | x = 1.0 33 | else: 34 | x = 2.0 35 | 36 | if condition: 37 | x = "abcd" 38 | else: 39 | x = "efgh" 40 | 41 | if condition: 42 | y = 10 43 | x = do_something(y) 44 | else: 45 | x = "efgh" 46 | ``` 47 | 48 | ```python 49 | x = 1 if condition else 2 50 | 51 | x = 1.0 if condition else 2.0 52 | 53 | x = "abcd" if condition else "efgh" 54 | 55 | if condition: 56 | y = 10 57 | x = do_something(y) 58 | else: 59 | x = "efgh" 60 | ``` 61 | -------------------------------------------------------------------------------- /.grit/patterns/python/thousands_separator.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Add thousands separator 3 | --- 4 | 5 | Add thousands separator (`1_000_000`) to numbers (ints and floats, positive or negative). 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language python 10 | 11 | or { 12 | integer(), 13 | float() 14 | } as $number where { 15 | or { 16 | and { 17 | $number <: r"(-?\d+)(\d{3})(\d{3})(\d{3})(\d{3})((?:\.\d+)?)$"($head, $g1, $g2, $g3, $g4, $fractional), 18 | $groups = [$head, $g1, $g2, $g3, $g4] 19 | }, 20 | and { 21 | $number <: r"(-?\d+)(\d{3})(\d{3})(\d{3})((?:\.\d+)?)$"($head, $g1, $g2, $g3, $fractional), 22 | $groups = [$head, $g1, $g2, $g3] 23 | }, 24 | and { 25 | $number <: r"(-?\d+)(\d{3})(\d{3})((?:\.\d+)?)$"($head, $g1, $g2, $fractional), 26 | $groups = [$head, $g1, $g2] 27 | }, 28 | and { 29 | $number <: r"(-?\d+)(\d{3})((?:\.\d+)?)$"($head, $group, $fractional), 30 | $groups = [$head, $group] 31 | } 32 | }, 33 | $formatted = join(list=$groups, separator="_"), 34 | $formatted = join(list=[$formatted, $fractional], separator="") 35 | } => `$formatted` 36 | ``` 37 | 38 | ## Add thousands separator to number 39 | 40 | ```python 41 | for n in range(1000): 42 | pass 43 | 44 | n = -1000 - 2000 45 | n = 1000000000 46 | n = 123456789123456 47 | 48 | x = 1000.123 49 | x = 1000000000.123 50 | x = -123456789123456.123 51 | 52 | m = 1000000000000000 53 | 54 | # Left as is 55 | 56 | n = 999 57 | x = 999.0 58 | ``` 59 | 60 | ```python 61 | for n in range(1_000): 62 | pass 63 | 64 | n = -1_000 - 2_000 65 | n = 1_000_000_000 66 | n = 123_456_789_123_456 67 | 68 | x = 1_000.123 69 | x = 1_000_000_000.123 70 | x = -123_456_789_123_456.123 71 | 72 | m = 1000_000_000_000_000 73 | 74 | # Left as is 75 | 76 | n = 999 77 | x = 999.0 78 | ``` 79 | -------------------------------------------------------------------------------- /.grit/patterns/python/use_defusedcsv.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Replace `csv` ⇒ `defusedcsv` 3 | tags: [fix, security] 4 | --- 5 | 6 | If you're generating a CSV file using the built-in `csv` module and incorporating user data, there's a potential security risk. An attacker might inject a formula into the CSV file, which, when imported into a spreadsheet application, could execute a malicious script, leading to data theft or even malware installation on the user's computer. To enhance security, consider using `defusedcsv` as a direct substitute for `csv`. `defusedcsv` maintains the same API but aims to thwart formula injection attempts, providing a safer way to create CSV files. 7 | 8 | ### references 9 | 10 | - [defusedcsv](https://github.com/raphaelm/defusedcsv) 11 | - [CSV_Injection](https://owasp.org/www-community/attacks/CSV_Injection) 12 | - [comma-separated-vulnerabilities](https://web.archive.org/web/20220516052229/https://www.contextis.com/us/blog/comma-separated-vulnerabilities) 13 | 14 | ```grit 15 | engine marzano(0.1) 16 | language python 17 | 18 | `import csv` => `import defusedcsv as csv` 19 | ``` 20 | 21 | ## without use-defusedcsv 22 | 23 | ```python 24 | # use-defusedcsv 25 | import csv 26 | 27 | with open("file", 'r') as fin: 28 | reader = csv.reader(fin) 29 | 30 | with open("file", 'w') as fout: 31 | writer = csv.writer(fout, quoting=csv.QUOTE_ALL) 32 | ``` 33 | 34 | ```python 35 | # use-defusedcsv 36 | import defusedcsv as csv 37 | 38 | with open("file", 'r') as fin: 39 | reader = csv.reader(fin) 40 | 41 | with open("file", 'w') as fout: 42 | writer = csv.writer(fout, quoting=csv.QUOTE_ALL) 43 | ``` 44 | 45 | ## with use-defusedcsv 46 | 47 | ```python 48 | # use-defusedcsv 49 | import defusedcsv as csv 50 | 51 | with open("file", 'w') as fout: 52 | writer = csv.writer(fout) 53 | ``` 54 | -------------------------------------------------------------------------------- /.grit/patterns/python/use_flask_jsonify.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: use `flask.jsonify()` 3 | tags: [fix, best-pactice, flask] 4 | --- 5 | 6 | `flask.jsonify()` simplifies returning `JSON` from `Flask` routes by automatically serializing Python objects into JSON format and setting the appropriate Content-Type header, resulting in cleaner and more readable code while ensuring consistency and compatibility with web standards. 7 | 8 | ### references 9 | 10 | - [flask.json.jsonify](https://flask.palletsprojects.com/en/2.2.x/api/#flask.json.jsonify) 11 | 12 | ```grit 13 | engine marzano(0.1) 14 | language python 15 | 16 | `@app.route($routesParams) 17 | def $func($funcParams):$funcBody` where { 18 | $import = "jsonify", 19 | $import <: ensure_import_from(source=`flask`), 20 | $funcBody <: contains or { 21 | `return json.dumps($user_dict)`, 22 | `return dumps($user_dict)` 23 | } => `return jsonify($user_dict)` 24 | } 25 | ``` 26 | 27 | ## with `json.dumps` 28 | 29 | ```python 30 | import flask 31 | import json 32 | app = flask.Flask(__name__) 33 | 34 | @app.route("/user") 35 | def user(): 36 | user_dict = get_user(request.args.get("id")) 37 | return json.dumps(user_dict) 38 | ``` 39 | 40 | ```python 41 | import flask 42 | import json 43 | from flask import jsonify 44 | 45 | app = flask.Flask(__name__) 46 | 47 | @app.route("/user") 48 | def user(): 49 | user_dict = get_user(request.args.get("id")) 50 | return jsonify(user_dict) 51 | ``` 52 | 53 | ## with dumps 54 | 55 | ```python 56 | from json import dumps 57 | 58 | @app.route("/user") 59 | def user(): 60 | user_dict = get_user(request.args.get("id")) 61 | # ruleid:use-jsonify 62 | return dumps(user_dict) 63 | ``` 64 | 65 | ```python 66 | from json import dumps 67 | from flask import jsonify 68 | 69 | @app.route("/user") 70 | def user(): 71 | user_dict = get_user(request.args.get("id")) 72 | # ruleid:use-jsonify 73 | return jsonify(user_dict) 74 | ``` 75 | 76 | ## normal function 77 | 78 | ```python 79 | def dumps(): 80 | pass 81 | def test_empty_dumps(): 82 | dumps() 83 | ``` 84 | -------------------------------------------------------------------------------- /.grit/patterns/python/warning_for_hardcoded_tmp_path.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Warning for hardcoded tmp path 3 | tags: [warning, good-practice] 4 | --- 5 | 6 | Detected hardcoded temp directory. Consider using `tempfile.TemporaryFile` instead 7 | 8 | - [reference](https://docs.python.org/3/library/tempfile.html#tempfile.TemporaryFile) 9 | 10 | 11 | ```grit 12 | engine marzano(0.1) 13 | language python 14 | 15 | or { 16 | `$file = open($url, $mode)`, 17 | `with open($url, $mode) as $filePath: $body` 18 | } as $readPath where { 19 | $url <: contains r"(^\/tmp.*)"($badString) 20 | } => `# BAD: hardcoded tmp path \n$readPath` 21 | ``` 22 | 23 | ## Warning for hardcoded tmp path 24 | 25 | ```python 26 | def test1(): 27 | f = open("/tmp/blah.txt", 'w') 28 | f.write("hello world") 29 | f.close() 30 | 31 | def test2(): 32 | f = open("/tmp/blah/blahblah/blah.txt", 'r') 33 | data = f.read() 34 | f.close() 35 | 36 | def test3(): 37 | f = open("./tmp/blah.txt", 'w') 38 | f.write("hello world") 39 | f.close() 40 | 41 | def test3a(): 42 | f = open("/var/log/something/else/tmp/blah.txt", 'w') 43 | f.write("hello world") 44 | f.close() 45 | 46 | def test4(): 47 | with open("/tmp/blah.txt", 'r') as fin: 48 | data = fin.read() 49 | 50 | def test5(): 51 | with open("./tmp/blah.txt", 'w') as fout: 52 | fout.write("hello world") 53 | ``` 54 | 55 | ```python 56 | def test1(): 57 | # BAD: hardcoded tmp path 58 | f = open("/tmp/blah.txt", 'w') 59 | f.write("hello world") 60 | f.close() 61 | 62 | def test2(): 63 | # BAD: hardcoded tmp path 64 | f = open("/tmp/blah/blahblah/blah.txt", 'r') 65 | data = f.read() 66 | f.close() 67 | 68 | def test3(): 69 | f = open("./tmp/blah.txt", 'w') 70 | f.write("hello world") 71 | f.close() 72 | 73 | def test3a(): 74 | f = open("/var/log/something/else/tmp/blah.txt", 'w') 75 | f.write("hello world") 76 | f.close() 77 | 78 | def test4(): 79 | # BAD: hardcoded tmp path 80 | with open("/tmp/blah.txt", 'r') as fin: 81 | data = fin.read() 82 | 83 | def test5(): 84 | with open("./tmp/blah.txt", 'w') as fout: 85 | fout.write("hello world") 86 | ``` 87 | -------------------------------------------------------------------------------- /.grit/patterns/rust/_test_todo.md: -------------------------------------------------------------------------------- 1 | # Test Rust todo 2 | 3 | This is just a test. 4 | 5 | ```grit 6 | language rust 7 | 8 | `if $z { 9 | $_ 10 | }` as $cond => todo($cond, "Consider using a match instead") 11 | ``` 12 | 13 | ## Multi-line 14 | 15 | ```rust 16 | if x { 17 | y 18 | } 19 | ``` 20 | 21 | ```rust 22 | todo!("Consider using a match instead"); 23 | if x { 24 | y 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /.grit/patterns/rust/byte_count_to_len.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Replace `str::bytes().count()` with `str::len()` 3 | tags: [clippy] 4 | --- 5 | 6 | `str::bytes().count()` is longer and may not be as performant as using `str::len()`. 7 | 8 | 9 | ```grit 10 | language rust 11 | 12 | `$hello.bytes().count()` => `$hello.len()` 13 | ``` 14 | 15 | ## Replaces a simple `str::bytes().count()` 16 | 17 | ```rust 18 | let my_len = "hello".bytes().count(); 19 | ``` 20 | 21 | ```rust 22 | let my_len = "hello".len(); 23 | ``` 24 | -------------------------------------------------------------------------------- /.grit/patterns/rust/cargo_use_long_dependency.md: -------------------------------------------------------------------------------- 1 | # Avoid version shorthand for dependencies 2 | 3 | In Cargo.toml files, switch from `name = version` to `name = { version = version }` to make it easier to read and maintain dependencies. 4 | 5 | ```grit 6 | language toml 7 | 8 | `[dependencies] 9 | $deps` where { 10 | $filename <: or { 11 | includes "Cargo.toml", 12 | includes "cargo.toml" 13 | }, 14 | $deps <: some bubble `$name = $version` where { 15 | $version <: string(), 16 | $version => `{ version = $version }` 17 | } 18 | } 19 | ``` 20 | 21 | ## Basic example 22 | 23 | Old syntax, with a mix of both: 24 | 25 | ```toml 26 | # @filename: Cargo.toml 27 | [package] 28 | name = "my-package" 29 | 30 | [dependencies] 31 | rand = "0.6" 32 | serde = { version = "1.0" } 33 | openssl = { version = "0.10" } 34 | other_pkg = "0.1.3" 35 | ``` 36 | 37 | New syntax, with all dependencies using the same format: 38 | 39 | ```toml 40 | # @filename: Cargo.toml 41 | [package] 42 | name = "my-package" 43 | 44 | [dependencies] 45 | rand = { version = "0.6" } 46 | serde = { version = "1.0" } 47 | openssl = { version = "0.10" } 48 | other_pkg = { version = "0.1.3" } 49 | ``` 50 | 51 | ## Ignore non-Cargo.toml files 52 | 53 | This rule only applies to Cargo.toml files, so it's safe to ignore other files. 54 | 55 | ```toml 56 | # @filename: other-file.toml 57 | [dependencies] 58 | rand = "0.6" 59 | ``` 60 | -------------------------------------------------------------------------------- /.grit/patterns/rust/collapsible_if.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Combine the conditions of nested if statements 3 | tags: [clippy] 4 | --- 5 | 6 | Redundant layers of nesting add undesirable complexity. 7 | 8 | 9 | ```grit 10 | language rust 11 | 12 | if_expression($condition, $consequence) where { 13 | $consequence <: block($content) where { 14 | $content <: [$inner], 15 | $inner <: expression_statement(expression=if_expression(condition=$inner_condition, consequence=block(content=$inner_content)) as $inner_if) where { 16 | $condition += ` && $inner_condition`, 17 | $inner_if => $inner_content 18 | } 19 | } 20 | } 21 | ``` 22 | 23 | ## Combines nested if 24 | 25 | ```rust 26 | let x = 6; 27 | if x > 3 { 28 | if x < 10 { 29 | println!("Hello"); 30 | } 31 | } 32 | ``` 33 | 34 | ```rust 35 | let x = 6; 36 | if x > 3 && x < 10 { 37 | println!("Hello"); 38 | } 39 | ``` 40 | 41 | ## Does not combine if statements with side effects 42 | 43 | ```rust 44 | let x = 6; 45 | if x > 3 { 46 | println!("Wow!"); 47 | if x < 10 { 48 | println!("Hello"); 49 | } 50 | } 51 | ``` 52 | 53 | ## Does not combine if statements with following side effects 54 | 55 | ```rust 56 | let x = 6; 57 | if x > 3 { 58 | if x < 10 { 59 | println!("Hello"); 60 | } 61 | println!("Wow!"); 62 | } 63 | ``` 64 | 65 | ## Combines nested if in else block 66 | 67 | ```rust 68 | let x = 6; 69 | if x > 7 { 70 | panic!("Too big!"); 71 | } else if x > 3 { 72 | if x < 10 { 73 | println!("Hello"); 74 | } 75 | } 76 | ``` 77 | 78 | ```rust 79 | let x = 6; 80 | if x > 7 { 81 | panic!("Too big!"); 82 | } else if x > 3 && x < 10 { 83 | println!("Hello"); 84 | } 85 | ``` 86 | -------------------------------------------------------------------------------- /.grit/patterns/rust/collapsible_match.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Collapse redundant match arms for readability 3 | tags: [clippy] 4 | --- 5 | 6 | Finds nested match expressions where the patterns may be combined to reduce the number of branches. 7 | 8 | 9 | ```grit 10 | language rust 11 | 12 | `match $var { 13 | $outer($_) => match $_ { 14 | $inner($inner_var) => $matched, 15 | $_ => $fallthrough, 16 | } 17 | $_ => $fallthrough, 18 | }` => `match $var { 19 | $outer($inner($inner_var)) => $matched, 20 | _ => $fallthrough, 21 | }` 22 | ``` 23 | 24 | ## Combines nested match 25 | 26 | ```rust 27 | fn func(opt: Option>) { 28 | let n = match opt { 29 | Some(n) => match n { 30 | Ok(n) => n, 31 | Err => return, 32 | } 33 | None => return, 34 | }; 35 | } 36 | ``` 37 | 38 | ```rust 39 | fn func(opt: Option>) { 40 | let n = match opt { 41 | Some(Ok(n)) => n, 42 | _ => return, 43 | }; 44 | } 45 | ``` 46 | 47 | ## Does not combine arms with different expressions 48 | 49 | ```rust 50 | fn func(opt: Option>) { 51 | let n = match opt { 52 | Some(n) => match n { 53 | Ok(n) => n, 54 | Err => 5, 55 | } 56 | None => return, 57 | }; 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /.grit/patterns/rust/no_needless_return.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remove return statements at the end of a block 3 | tags: [clippy] 4 | --- 5 | 6 | It is more idiomatic to remove the return keyword and the semicolon. 7 | 8 | 9 | ```grit 10 | language rust 11 | 12 | function_item(body=$block) where { 13 | $block <: block($content) where { 14 | $content <: [$..., $last_statement] where { 15 | $last_statement <: `return $x;` => $x 16 | } 17 | } 18 | } 19 | ``` 20 | 21 | ## Removes return at the end of a block 22 | 23 | ```rust 24 | fn foo(x: usize) -> usize { 25 | println!("Hi!"); 26 | return x; 27 | } 28 | ``` 29 | 30 | ```rust 31 | fn foo(x: usize) -> usize { 32 | println!("Hi!"); 33 | x 34 | } 35 | ``` 36 | 37 | ## Does not remove return in the middle of a block 38 | 39 | ```rust 40 | fn foo(x: usize) -> usize { 41 | if x == 0 { 42 | return x + 1; 43 | } 44 | return x; 45 | } 46 | ``` 47 | 48 | ```rust 49 | fn foo(x: usize) -> usize { 50 | if x == 0 { 51 | return x + 1; 52 | } 53 | x 54 | } 55 | ``` 56 | 57 | ## Removes ending return with complex expression 58 | 59 | ```rust 60 | fn foo(x: usize) -> usize { 61 | println!("Hi!"); 62 | return if x == 0 { 63 | x + 1 64 | } else { 65 | x 66 | }; 67 | } 68 | ``` 69 | 70 | ```rust 71 | fn foo(x: usize) -> usize { 72 | println!("Hi!"); 73 | if x == 0 { 74 | x + 1 75 | } else { 76 | x 77 | } 78 | } 79 | ``` 80 | -------------------------------------------------------------------------------- /.grit/patterns/rust/no_useless_format.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Replace unnecessary format macro with `to_string()` 3 | tags: [clippy] 4 | --- 5 | 6 | Checks for the use of `format!("string literal with no argument")` and `format!("{}", foo)` where foo is a string. 7 | 8 | ```grit 9 | language rust 10 | 11 | or { 12 | `format!($content)` where { 13 | $content <: string_literal() 14 | } => `$content.to_string()`, 15 | `format!("{}", $arg)` => `$arg.to_string()` 16 | } 17 | ``` 18 | 19 | ## Replaces a string literal with no argument 20 | 21 | ```rust 22 | let hi = format!("hello"); 23 | ``` 24 | 25 | ```rust 26 | let hi = "hello".to_string(); 27 | ``` 28 | 29 | ## Replaces with one string literal argument 30 | 31 | ```rust 32 | let greeting = "hello"; 33 | let hi = format!("{}", greeting); 34 | ``` 35 | 36 | ```rust 37 | let greeting = "hello"; 38 | let hi = greeting.to_string(); 39 | ``` 40 | 41 | ## Does not replace necessary formats 42 | 43 | ```rust 44 | let hi = format!("hello {}", "world"); 45 | let another = format!("{:?}", strawberry); 46 | ``` 47 | -------------------------------------------------------------------------------- /.grit/patterns/rust/rust_todo.grit: -------------------------------------------------------------------------------- 1 | language rust 2 | 3 | function todo($target, $message) { 4 | if ($message <: undefined) { 5 | $message = "This requires manual intervention." 6 | }, 7 | $log_message = `TODO: $message`, 8 | log(message=$log_message, variable=$target), 9 | return `todo!("$message"); 10 | $target` 11 | } 12 | -------------------------------------------------------------------------------- /.grit/patterns/rust/use_secure_hashes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Use secure hashes 3 | tags: [security, hash] 4 | --- 5 | 6 | The hashing functions `md2`, `md4`, `md5`, and `sha1` are detected as cryptographically insecure due to known vulnerabilities. It is advisable to use more secure hashing algorithms for cryptographic purposes. 7 | 8 | references: 9 | 10 | - [RustCrypto](https://github.com/RustCrypto/hashes) 11 | - [md2](https://docs.rs/md2/latest/md2/) 12 | - [md4](https://docs.rs/md4/latest/md4/) 13 | - [md5](https://docs.rs/md5/latest/md5/) 14 | - [sha1](https://docs.rs/sha-1/latest/sha1/) 15 | 16 | ```grit 17 | language rust 18 | 19 | or { 20 | `Md2::new()`, 21 | `Md4::new()`, 22 | `Md5::new()`, 23 | `Sha1::new()` 24 | } => `Sha256::new()` 25 | ``` 26 | 27 | ## With Md2 28 | 29 | ```rust 30 | let mut hasher = Md2::new(); 31 | ``` 32 | 33 | ```rust 34 | let mut hasher = Sha256::new(); 35 | ``` 36 | 37 | ## With Md4 38 | 39 | ```rust 40 | let mut hasher = Md4::new(); 41 | ``` 42 | 43 | ```rust 44 | let mut hasher = Sha256::new(); 45 | ``` 46 | 47 | ## With Md5 48 | 49 | ```rust 50 | let mut hasher = Md5::new(); 51 | ``` 52 | 53 | ```rust 54 | let mut hasher = Sha256::new(); 55 | ``` 56 | 57 | ## With sha1 58 | 59 | ```rust 60 | let mut hasher = Sha1::new(); 61 | ``` 62 | 63 | ```rust 64 | let mut hasher = Sha256::new(); 65 | ``` 66 | 67 | ## With sha256 68 | 69 | ```rust 70 | let mut hasher = Sha256::new(); 71 | ``` 72 | -------------------------------------------------------------------------------- /.grit/patterns/solidity/NestedLoop.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Nested Loop 3 | tags: [reentrancy, vulnerability] 4 | --- 5 | 6 | Inspect nested loops. 7 | 8 | 9 | ```grit 10 | language sol 11 | 12 | pattern loop($body) { 13 | bubble($body) or { 14 | `while($_) { $body }`, 15 | `for ($_; $_; $_) { $body }` 16 | } 17 | } 18 | 19 | loop($body) where $body <: contains loop(body=$_) 20 | ``` 21 | 22 | ## Matches a simple nested loop 23 | 24 | ```Solidity 25 | // SPDX-License-Identifier: MIT 26 | // compiler version must be greater than or equal to 0.8.13 and less than 0.9.0 27 | pragma solidity ^0.8.9; 28 | 29 | contract HelloWorld { 30 | string public greet = "Hello World!"; 31 | 32 | function foo(string memory _greet) public { 33 | while(other) { 34 | greet = foo(bar); 35 | while(foo) { 36 | greet = foo(bar); 37 | } 38 | } 39 | } 40 | } 41 | ``` 42 | ```Solidity 43 | // SPDX-License-Identifier: MIT 44 | // compiler version must be greater than or equal to 0.8.13 and less than 0.9.0 45 | pragma solidity ^0.8.9; 46 | 47 | contract HelloWorld { 48 | string public greet = "Hello World!"; 49 | 50 | function foo(string memory _greet) public { 51 | while(other) { 52 | greet = foo(bar); 53 | while(foo) { 54 | greet = foo(bar); 55 | } 56 | } 57 | } 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /.grit/patterns/solidity/NoMulDivRoundUp.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: NoMulDiv 3 | --- 4 | 5 | Say we do not want `mulDivRoundUp`. 6 | 7 | ```grit 8 | language sol 9 | 10 | `$_.mulDivRoundUp($amount, $_)` 11 | ``` 12 | 13 | ## Matches a simple mulDivRoundUp 14 | 15 | ```Solidity 16 | // SPDX-License-Identifier: MIT 17 | // compiler version must be greater than or equal to 0.8.13 and less than 0.9.0 18 | pragma solidity ^0.8.9; 19 | 20 | contract HelloWorld { 21 | string public greet = "Hello World!"; 22 | 23 | function claim( 24 | uint256 numPasses, 25 | uint256 amount, 26 | uint256 mpIndex, 27 | bytes32[] calldata merkleProof 28 | ) external payable { 29 | require(isValidClaim(numPasses,amount,mpIndex,merkleProof)); 30 | 31 | //return any excess funds to sender if overpaid 32 | uint256 excessPayment = msg.value.sub(numPasses.mul(mintPasses[mpIndex].mintPrice)); 33 | (bool returnExcessStatus, ) = _msgSender().call{value: excessPayment}(""); 34 | 35 | mintPasses[mpIndex].claimedMPs[msg.sender] = mintPasses[mpIndex].mulDivRoundUp(numPasses, 3); 36 | _mint(msg.sender, mpIndex, numPasses, ""); 37 | emit Claimed(mpIndex, msg.sender, numPasses); 38 | } 39 | } 40 | ``` 41 | ```Solidity 42 | // SPDX-License-Identifier: MIT 43 | // compiler version must be greater than or equal to 0.8.13 and less than 0.9.0 44 | pragma solidity ^0.8.9; 45 | 46 | contract HelloWorld { 47 | string public greet = "Hello World!"; 48 | 49 | function claim( 50 | uint256 numPasses, 51 | uint256 amount, 52 | uint256 mpIndex, 53 | bytes32[] calldata merkleProof 54 | ) external payable { 55 | require(isValidClaim(numPasses,amount,mpIndex,merkleProof)); 56 | 57 | //return any excess funds to sender if overpaid 58 | uint256 excessPayment = msg.value.sub(numPasses.mul(mintPasses[mpIndex].mintPrice)); 59 | (bool returnExcessStatus, ) = _msgSender().call{value: excessPayment}(""); 60 | 61 | mintPasses[mpIndex].claimedMPs[msg.sender] = mintPasses[mpIndex].mulDivRoundUp(numPasses, 3); 62 | _mint(msg.sender, mpIndex, numPasses, ""); 63 | emit Claimed(mpIndex, msg.sender, numPasses); 64 | } 65 | } 66 | ``` 67 | -------------------------------------------------------------------------------- /.grit/patterns/solidity/UpgradableProxyPattern.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Upgradable Proxy Pattern 3 | tags: [reentrancy, vulnerability] 4 | --- 5 | 6 | Looking for variations of the upgradable proxy pattern. 7 | 8 | 9 | ```grit 10 | language sol 11 | 12 | contract_declaration(heritage=$inherited) where { 13 | $inherited <: contains or { 14 | "Proxy", 15 | "ERC1967Upgrade", 16 | "TransparentUpgradeableProxy", 17 | "UUPSUpgradeable" 18 | } 19 | } 20 | ``` 21 | 22 | ## Simple class extending UUPSUpgradeable 23 | 24 | ```Solidity 25 | // SPDX-License-Identifier: MIT 26 | // compiler version must be greater than or equal to 0.8.13 and less than 0.9.0 27 | pragma solidity ^0.8.9; 28 | 29 | contract HelloWorld is UUPSUpgradeable, Another { 30 | string public greet = "Hello World!"; 31 | 32 | function foo(string memory _greet) public { 33 | greet = foo(bar); 34 | } 35 | } 36 | 37 | ``` 38 | 39 | ```Solidity 40 | // SPDX-License-Identifier: MIT 41 | // compiler version must be greater than or equal to 0.8.13 and less than 0.9.0 42 | pragma solidity ^0.8.9; 43 | 44 | contract HelloWorld is UUPSUpgradeable, Another { 45 | string public greet = "Hello World!"; 46 | 47 | function foo(string memory _greet) public { 48 | greet = foo(bar); 49 | } 50 | } 51 | 52 | ``` 53 | -------------------------------------------------------------------------------- /.grit/patterns/sql/add_pg_unit_test.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Add PgTAP unit test for procedure 3 | --- 4 | 5 | [PgTAP](https://pgtap.org/) is a unit testing framework for Postgres. This pattern adds a unit test checking a procedure has been correctly defined. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language sql 10 | 11 | pattern add_unit_tests_for_procedures() { 12 | `CREATE PROCEDURE $proc_name($args) AS $decl $block;` where { 13 | $program += ` 14 | 15 | -- Check that '$proc_name' has been translated into valid plpgsql 16 | SELECT has_function('$proc_name'); 17 | SELECT is_procedure('$proc_name'); 18 | SELECT function_lang_is('$proc_name', 'pgplsql' ); 19 | ` 20 | } 21 | } 22 | 23 | add_unit_tests_for_procedures() 24 | ``` 25 | 26 | ## Basic procedure 27 | 28 | ```sql 29 | CREATE PROCEDURE remove_emp(employee_id int) AS 30 | tot_emps int; 31 | BEGIN 32 | DELETE FROM employees 33 | WHERE employees.employee_id = remove_emp.employee_id; 34 | tot_emps := tot_emps - 1; 35 | END; 36 | ``` 37 | 38 | ```sql 39 | CREATE PROCEDURE remove_emp(employee_id int) AS 40 | tot_emps int; 41 | BEGIN 42 | DELETE FROM employees 43 | WHERE employees.employee_id = remove_emp.employee_id; 44 | tot_emps := tot_emps - 1; 45 | END; 46 | 47 | 48 | -- Check that 'remove_emp' has been translated into valid plpgsql 49 | SELECT has_function('remove_emp'); 50 | SELECT is_procedure('remove_emp'); 51 | SELECT function_lang_is('remove_emp', 'pgplsql' ); 52 | ``` 53 | -------------------------------------------------------------------------------- /.grit/patterns/sql/oracle_nextval_to_pg.wip: -------------------------------------------------------------------------------- 1 | --- 2 | title: Convert Oracle sequence.nextval to PG 3 | --- 4 | 5 | Converts PLSQL `sequence.nextval` into Postgres `nextval(sequence)` syntax. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language sql 10 | 11 | pattern rewrite_nextval() { 12 | `$sequence.nextval` => `nextval($sequence)` 13 | } 14 | 15 | rewrite_nextval() 16 | ``` 17 | 18 | ## Rewrite sequence.nextval 19 | 20 | ```sql 21 | INSERT INTO employees VALUES (employee_ids_seq.nextval, 'First', 'Last') ; 22 | ``` 23 | 24 | ```sql 25 | INSERT INTO employees VALUES (nextval(employee_ids_seq), 'First', 'Last') ; 26 | ``` 27 | -------------------------------------------------------------------------------- /.grit/patterns/sql/oracle_quote_procedure.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Oracle to PG: Dollar quote stored procedure body" 3 | --- 4 | 5 | In Postgres, function and procedure bodies need to be wrapped in $$dollar quotes$$. 6 | This pattern wraps a PLSQL `CREATE PROCEDURE` body in dollar quotes and adds a language specifier. 7 | 8 | ```grit 9 | engine marzano(0.1) 10 | language sql 11 | 12 | pattern dollar_quote_procedure_body() { 13 | `CREATE PROCEDURE $name($args) AS $decl $block;` as $proc where { 14 | $block => `$$$block;\n$$ LANGUAGE plpgsql`, 15 | $decl => `DECLARE\n $decl` 16 | } 17 | } 18 | dollar_quote_procedure_body() 19 | ``` 20 | 21 | ## Basic procedure 22 | 23 | ```sql 24 | CREATE PROCEDURE remove_emp (employee_id int) AS 25 | tot_emps int; 26 | BEGIN 27 | DELETE FROM employees 28 | WHERE employees.employee_id = remove_emp.employee_id; 29 | tot_emps := tot_emps - 1; 30 | END; 31 | ``` 32 | 33 | ```sql 34 | CREATE PROCEDURE remove_emp (employee_id int) AS 35 | DECLARE 36 | tot_emps int; 37 | $$BEGIN 38 | DELETE FROM employees 39 | WHERE employees.employee_id = remove_emp.employee_id; 40 | tot_emps := tot_emps - 1; 41 | END; 42 | $$ LANGUAGE plpgsql; 43 | ``` 44 | -------------------------------------------------------------------------------- /.grit/patterns/sql/oracle_to_pg.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: (WIP) Convert Oracle PL/SQL syntax into PL/pgSQL 3 | --- 4 | 5 | This pattern combines several smaller patterns 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language sql 10 | 11 | convert_oracle_to_pg() 12 | ``` 13 | 14 | ## example 15 | 16 | ```sql 17 | CREATE TABLE employees 18 | ( 19 | first_name VARCHAR2(128), 20 | last_name VARCHAR2(128), 21 | empID NUMBER, 22 | salary NUMBER(6), 23 | pkey NUMBER(12,0), 24 | category_name VARCHAR2(15 BYTE), 25 | boid VARCHAR2(40 CHAR), 26 | info CLOB, 27 | data BLOB, 28 | -- my_interval INTERVAL DAY TO SECOND, 29 | pct_complete FLOAT, 30 | updated_at TIMESTAMP(9), 31 | config XMLTYPE 32 | ); 33 | 34 | CREATE PROCEDURE remove_emp(employee_id NUMBER) AS 35 | tot_emps NUMBER; 36 | BEGIN 37 | DELETE FROM employees 38 | WHERE employees.employee_id = remove_emp.employee_id; 39 | tot_emps := tot_emps - 1; 40 | END; 41 | ``` 42 | 43 | ```sql 44 | CREATE TABLE employees 45 | ( 46 | first_name VARCHAR(128), 47 | last_name VARCHAR(128), 48 | empID NUMERIC, 49 | salary NUMERIC(6), 50 | pkey NUMERIC(12,0), 51 | category_name VARCHAR(15 BYTE), 52 | boid VARCHAR(40), 53 | info TEXT, 54 | data BYTEA, 55 | -- my_interval INTERVAL DAY TO SECOND, 56 | pct_complete DOUBLE PRECISION, 57 | updated_at TIMESTAMP(9), 58 | config XML 59 | ); 60 | 61 | CREATE PROCEDURE remove_emp(employee_id NUMERIC) AS 62 | DECLARE 63 | tot_emps NUMERIC; 64 | $$BEGIN 65 | DELETE FROM employees 66 | WHERE employees.employee_id = remove_emp.employee_id; 67 | tot_emps := tot_emps - 1; 68 | END; 69 | $$ LANGUAGE plpgsql; 70 | 71 | 72 | -- Check that 'remove_emp' has been translated into valid plpgsql 73 | SELECT has_function('remove_emp'); 74 | SELECT is_procedure('remove_emp'); 75 | SELECT function_lang_is('remove_emp', 'pgplsql' ); 76 | ``` 77 | -------------------------------------------------------------------------------- /.grit/patterns/terraform/kv_pair.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Key value pair 3 | --- 4 | 5 | Find a key-value pair in Terraform HCL. 6 | 7 | ```grit 8 | engine marzano(0.1) 9 | language hcl 10 | 11 | `$arg: $red` 12 | ``` 13 | 14 | ## Matches a key-value pair 15 | 16 | ```hcl 17 | default_address = "127.0.0.1" 18 | default_message = upper("Incident: ${incident}") 19 | default_options = { 20 | priority: "High", 21 | color: "Red" 22 | } 23 | 24 | incident_rules { 25 | # Rule number 1 26 | rule "down_server" "infrastructure" { 27 | incident = 100 28 | options = var.override_options ? var.override_options : var.default_options 29 | server = default_address 30 | message = default_message 31 | } 32 | } 33 | ``` 34 | 35 | ```hcl 36 | default_address = "127.0.0.1" 37 | default_message = upper("Incident: ${incident}") 38 | default_options = { 39 | priority: "High", 40 | color: "Red" 41 | } 42 | 43 | incident_rules { 44 | # Rule number 1 45 | rule "down_server" "infrastructure" { 46 | incident = 100 47 | options = var.override_options ? var.override_options : var.default_options 48 | server = default_address 49 | message = default_message 50 | } 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /.grit/patterns/universal/ai/_ai_generate.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: [ai, sample, util, hidden, example, docs] 3 | --- 4 | 5 | # AI generate 6 | 7 | GritQL can generate new code based on some instruction using the `ai_generate` function. 8 | 9 | Just call ai_generate with your instructions to assign a value to a variable. Metavariables can be referenced in the instructions 10 | 11 | ```grit 12 | language js 13 | 14 | `console.log($msg)` where { 15 | $level = ai_generate(`Based on this log message: $msg, generate a new log level of either "info", "warn", or "error" with only one word`) 16 | } => `logger.level("$level", $msg)` 17 | ``` 18 | 19 | ## Rewrites a basic console.log 20 | 21 | ```js 22 | console.log('Hello, world!'); 23 | ``` 24 | 25 | ```js 26 | logger.level('info', 'Hello, world!'); 27 | ``` 28 | -------------------------------------------------------------------------------- /.grit/patterns/universal/ai/_ai_is_bare.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: [ai, sample, util, hidden, flaky] 3 | --- 4 | 5 | # AI conditions. 6 | 7 | Test `ai_is` with no counter-examples. 8 | 9 | ```grit 10 | `console.log($msg)` => `// REDACTED: $msg` where { 11 | $msg <: ai_is("references personally identifiable information") 12 | } 13 | ``` 14 | 15 | # Tests disabled, this doesn't work reliably yet. 16 | 17 | ## Solve some basic cases 18 | 19 | ```js 20 | console.log('This is the system. It is fine.'); 21 | console.log('We are now processing the user. Their name is:', user.name); 22 | ``` 23 | 24 | ```ts 25 | console.log('This is the system. It is fine.'); 26 | // REDACTED: 'We are now processing the user. Their name is:', user.name; 27 | ``` 28 | 29 | ## With double quotes 30 | 31 | ```js 32 | console.log('This is the system. It is fine.'); 33 | console.log('We are now processing "the user". Their name is:', user.name); 34 | ``` 35 | 36 | ```ts 37 | console.log('This is the system. It is fine.'); 38 | // REDACTED: 'We are now processing "the user". Their name is:', user.name; 39 | ``` 40 | -------------------------------------------------------------------------------- /.grit/patterns/universal/ai/_ai_match.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: [ai, sample, util, hidden, flaky] 3 | --- 4 | 5 | # AI conditions. 6 | 7 | GritQL can use an AI for fuzzy matching. Just match the node you wish to analyze against the `ai_is` pattern. 8 | 9 | ```grit 10 | `console.log($msg)` => `// REDACTED: $msg` where { 11 | $msg <: ai_is("it references personally identifiable information") 12 | } 13 | ``` 14 | 15 | ## Solve some basic cases 16 | 17 | ```js 18 | console.log('This is the system. It is fine.'); 19 | console.log('We are now processing the user. Their name is:', user.name); 20 | ``` 21 | 22 | ```ts 23 | console.log('This is the system. It is fine.'); 24 | // REDACTED: 'We are now processing the user. Their name is:', user.name; 25 | ``` 26 | 27 | ## With double quotes 28 | 29 | ```js 30 | console.log('This is the system. It is fine.'); 31 | console.log('We are now processing "the user". Their name is:', user.name); 32 | ``` 33 | 34 | ```ts 35 | console.log('This is the system. It is fine.'); 36 | // REDACTED: 'We are now processing "the user". Their name is:', user.name; 37 | ``` 38 | -------------------------------------------------------------------------------- /.grit/patterns/universal/ai/_ai_transform_simple.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: [ai, sample, util, hidden, example, flaky] 3 | --- 4 | 5 | This is a simple test transformation to convert arrow functions to traditional functions 6 | 7 | ```grit 8 | language js 9 | 10 | `($_) => $_` as $arrow where { 11 | $arrow <: not contains `callOutside($_)` 12 | } => ai_rewrite($arrow, instruct="Convert all arrow function to traditional function syntax using `function`") 13 | ``` 14 | 15 | ## Simple test case 16 | 17 | ```js 18 | // This is my file 19 | const MY_VAR = 9; 20 | 21 | // This is my arrow function 22 | const myArrow = (a, b) => a + b; 23 | 24 | // This is my second arrow function 25 | const myArrow2 = (foo: string) => { 26 | console.log('Checking foo', foo); 27 | return foo.length; 28 | }; 29 | ``` 30 | 31 | ```js 32 | // This is my file 33 | const MY_VAR = 9; 34 | 35 | // This is my arrow function 36 | const myArrow = function (a, b) { 37 | return a + b; 38 | }; 39 | 40 | // This is my second arrow function 41 | const myArrow2 = function (foo: string) { 42 | console.log('Checking foo', foo); 43 | return foo.length; 44 | }; 45 | ``` 46 | 47 | ## Harder case 48 | 49 | This case has some arrow functions that should _NOT_ be modified. 50 | 51 | ```js 52 | // This is my file 53 | const MY_VAR = 9; 54 | 55 | // This is someone else's arrow function 56 | const theirArrow = (a, b) => { 57 | console.log('HELLO SIR'); 58 | callOutside('world'); 59 | return a % b; 60 | }; 61 | 62 | // This is my arrow function 63 | const myArrow = (a, b) => a + b; 64 | 65 | // This is my second arrow function 66 | const myArrow2 = (foo: string) => { 67 | console.log('Checking foo', foo); 68 | return foo.length; 69 | }; 70 | ``` 71 | 72 | ```js 73 | // This is my file 74 | const MY_VAR = 9; 75 | 76 | // This is someone else's arrow function 77 | const theirArrow = (a, b) => { 78 | console.log('HELLO SIR'); 79 | callOutside('world'); 80 | return a % b; 81 | }; 82 | 83 | // This is my arrow function 84 | const myArrow = function (a, b) { 85 | return a + b; 86 | }; 87 | 88 | // This is my second arrow function 89 | const myArrow2 = function (foo: string) { 90 | console.log('Checking foo', foo); 91 | return foo.length; 92 | }; 93 | ``` 94 | -------------------------------------------------------------------------------- /.grit/patterns/universal/ai/_js_transform.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: [ai, hidden, test, flaky] 3 | --- 4 | 5 | # AI transform - JS 6 | 7 | GritQL can use AI to transform a target variable based on some instruction using the `ai_transform` function. 8 | 9 | ```grit 10 | language js 11 | 12 | or { 13 | // It can replace constructs 14 | `console.log($_)` as $log where { 15 | $log => ai_transform(match=$log, instruct="Use console.error instead") 16 | }, 17 | // Inline replacements also work 18 | `function $_($args) { $_ }` where { 19 | $args => ai_transform(match=$args, instruct="Make the arguments uppercase") 20 | } 21 | } 22 | ``` 23 | 24 | ## Proof of sanity 25 | 26 | ```js 27 | const { grit } = require('grit'); 28 | console.log('Hello world!'); 29 | ``` 30 | 31 | ```js 32 | const { grit } = require('grit'); 33 | console.error('Hello world!'); 34 | ``` 35 | 36 | ## Inline replacement 37 | 38 | ```js 39 | function testing(arg1, arg2, arg3) { 40 | console.error('Hello world!'); 41 | } 42 | ``` 43 | 44 | ```js 45 | function testing(ARG1, ARG2, ARG3) { 46 | console.error('Hello world!'); 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /.grit/patterns/universal/ai/ai_choose_example.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: [ai, sample, util, hidden, flaky] 3 | --- 4 | 5 | # Ask an AI 6 | 7 | GritQL includes built-in support for querying an AI to answer questions in patterns via the `ai_choose` function. 8 | 9 | For example, you can use the `ai_choose` function to choose a name for a function. 10 | 11 | ```grit 12 | language js 13 | 14 | `function ($args) { $body }` as $func where { 15 | $name = ai_ask(question=`Should this function be an adder, divider, or remover? $func`, choices=or { 16 | `adder`, 17 | `divider`, 18 | `remover` 19 | }) 20 | } => `// This function: $name 21 | $func` 22 | ``` 23 | 24 | # WIP - not working yet 25 | 26 | ## Solve a basic case 27 | 28 | ```js 29 | function (x) { return x + 1; } 30 | ``` 31 | 32 | ```ts 33 | // This function: Ad 34 | function (x) { return x + 1; } 35 | ``` 36 | 37 | ## Divide too 38 | 39 | ```js 40 | function (x) { return x / 2; } 41 | ``` 42 | 43 | ```ts 44 | // This function: Div 45 | function (x) { return x / 2; } 46 | ``` 47 | 48 | ## With double quotes 49 | 50 | ```js 51 | function (x) { return x + ""; } 52 | ``` 53 | 54 | ```ts 55 | // This function: Ad 56 | function (x) { return x + ""; } 57 | ``` 58 | -------------------------------------------------------------------------------- /.grit/patterns/universal/ai/ai_rewrite.grit: -------------------------------------------------------------------------------- 1 | language universal 2 | 3 | // Transform the provided code to match the given instructions 4 | function ai_rewrite($match, $instruct) { 5 | if ($GLOBAL_REWRITE_INSTRUCTION <: not or { 6 | $instruct, 7 | $undefined 8 | }) { $match => `"Only a single ai_rewrite can be used per pattern."` } else { 9 | $GLOBAL_REWRITE_INSTRUCTION = $instruct, 10 | return `$match` 11 | } 12 | } 13 | 14 | pattern after_each_file_global_rewrites() { 15 | if ($GLOBAL_REWRITE_INSTRUCTION <: not undefined) { 16 | file($body) where { 17 | $code_with_markers = text($body, true), 18 | $instruct = $GLOBAL_REWRITE_INSTRUCTION, 19 | $messages = [ 20 | { 21 | role: 22 | "system" 23 | , 24 | content: 25 | raw`Your job is to help rewrite code based on user instructions. 26 | 27 | You will be given a file which has been annotated with specific ranges to modify. 28 | 29 | The target ranges will be marked with tags. 30 | 31 | Given the instructions, you should return a full copy of the file with the appropriate modifications applied based on the user instructions. 32 | 33 | The file should be surrounded by tags. You should include the full, modified file, in your response. 34 | ` 35 | }, 36 | 37 | { 38 | role: 39 | "user" 40 | , 41 | content: 42 | ` 43 | $instruct 44 | 45 | 46 | $code_with_markers 47 | ` 48 | }, 49 | 50 | { 51 | role: 52 | "assistant" 53 | , 54 | content: 55 | `Here is the file, with modifications made per your instructions. 56 | 57 | ` 58 | }, 59 | ], 60 | $answer = llm_chat($messages, $pattern, model="claude-3-5-sonnet-20240620", stop_sequences=[ 61 | "" 62 | ]), 63 | $answer <: includes r"(?:\s*)([\s\S]+?)\s*"($final), 64 | $body => `$final\n` 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.grit/patterns/universal/basic_example.grit: -------------------------------------------------------------------------------- 1 | engine marzano(0.1) 2 | language universal 3 | 4 | pattern foo_to_bar() { contains `foo` => `bar` } 5 | -------------------------------------------------------------------------------- /.grit/patterns/universal/blocks.grit: -------------------------------------------------------------------------------- 1 | language universal 2 | 3 | // Implementation 4 | function group_blocks($target) { 5 | $blocks = [], 6 | $target <: some bubble($blocks, $block, $block_tail) $current where { 7 | if ($block <: undefined) { 8 | $block = [$current], 9 | $block_tail = $current 10 | } else { 11 | // Are we right after the same block? 12 | if ($current <: after $block_tail) { 13 | $block += $current, 14 | $block_tail = $current 15 | } else { 16 | // Insert the previous block into the list 17 | $blocks += $block, 18 | $block = [$current], 19 | $block_tail = $current 20 | } 21 | } 22 | }, 23 | // Insert final block 24 | if (not $block <: undefined) { $blocks += $block }, 25 | return $blocks 26 | } 27 | -------------------------------------------------------------------------------- /.grit/patterns/universal/files.grit: -------------------------------------------------------------------------------- 1 | language universal 2 | 3 | // Useful utilities for working with files 4 | 5 | function current_filename() { 6 | $parts = split($filename, "/"), 7 | $final = $parts[-1], 8 | return $final 9 | } 10 | 11 | function strip_extension($original) { 12 | $parts = [], 13 | $original = split($original, "/"), 14 | $original <: some bubble($parts, $original) $part where { 15 | if ($part <: not $original[-1]) { $parts += $part } 16 | }, 17 | $last_parts = split($original[-1], "."), 18 | $length = length($last_parts), 19 | if ($length <: 1) { $parts += $last_parts } else { 20 | $dot_parts = [], 21 | $last_parts <: some $last_part where { 22 | if ($last_part <: not $last_parts[-1]) { $dot_parts += $last_part } 23 | }, 24 | $dot_parts = join($dot_parts, "."), 25 | $parts += $dot_parts 26 | }, 27 | return join($parts, "/") 28 | } 29 | 30 | function current_filename_without_extension() { 31 | $raw = current_filename(), 32 | $stripped = strip_extension($raw), 33 | return $stripped 34 | } 35 | -------------------------------------------------------------------------------- /.grit/patterns/universal/lists.grit: -------------------------------------------------------------------------------- 1 | language universal 2 | 3 | // Concat two lists together 4 | function concat($list_a, $list_b) { 5 | $new_list = [], 6 | $list_a <: some bubble($new_list) $item where $new_list += $item, 7 | $list_b <: some bubble($new_list) $item where $new_list += $item, 8 | return $new_list 9 | } 10 | 11 | function sort_internal($values) js { 12 | var q = $values.text.split("|||GRIT_SORT_SEP|||"); 13 | return q.sort().join("|||GRIT_SORT_SEP|||") 14 | } 15 | 16 | // Sort a list 17 | function sort($list) { 18 | $new_list = join($list, "|||GRIT_SORT_SEP|||"), 19 | $sorted = sort_internal($new_list), 20 | $split = split($sorted, "|||GRIT_SORT_SEP|||"), 21 | return $split 22 | } 23 | -------------------------------------------------------------------------------- /.grit/patterns/universal/replacements.grit: -------------------------------------------------------------------------------- 1 | function replace_all($haystack, $search, $replacement) js { 2 | const replacement = $haystack.text.replaceAll($search.text, $replacement.text); 3 | return replacement; 4 | } 5 | -------------------------------------------------------------------------------- /.grit/patterns/universal/sort_fn.md: -------------------------------------------------------------------------------- 1 | # Sort function 2 | 3 | The `sort($list)` function sorts a list of items in ascending order, based on the lexical order of the items. 4 | 5 | ```grit 6 | engine marzano(0.1) 7 | language python 8 | 9 | `list = [$items]` where { 10 | $new_items = sort($items), 11 | $joined = join($new_items, ", ") 12 | } => `list = [$joined]` 13 | ``` 14 | 15 | ## Test case 16 | 17 | ```python 18 | list = [a, c, b, "1,3,4"] 19 | ``` 20 | 21 | ```python 22 | list = ["1,3,4", a, b, c] 23 | ``` 24 | -------------------------------------------------------------------------------- /.grit/patterns/universal/test.grit: -------------------------------------------------------------------------------- 1 | language js 2 | 3 | // Just for testing transitive 4 | pattern fix_match_test() { `testMatch` => `testRegex` } 5 | -------------------------------------------------------------------------------- /.grit/patterns/universal/use_blocks.md: -------------------------------------------------------------------------------- 1 | # Blocks function 2 | 3 | The `group_blocks` function takes a `target` list and returns a list of lists, where each sublist is a block of items that are adjacent to each other in the original program. 4 | 5 | ```grit 6 | language js 7 | 8 | // Usage example 9 | file($body) where { 10 | $imports = [], 11 | $body <: contains bubble($imports) import_statement() as $import where { 12 | $imports += $import 13 | }, 14 | $grouped = group_blocks(target=$imports), 15 | $index = ``, 16 | $grouped <: some bubble($index) $block where { 17 | $index += `\n// new block\n`, 18 | $index += join(list=$block, separator=`\n`) 19 | }, 20 | $body <: contains `const insert = $_` => $index 21 | } 22 | ``` 23 | 24 | ## Test case 25 | 26 | ```js 27 | // Block one 28 | import { foo } from 'bar'; 29 | import { baz } from 'qux'; 30 | import * as food from 'quux'; 31 | import { corge } from 'grault'; 32 | // Block two 33 | import { quux } from 'corge'; 34 | import { grault } from 'garply'; 35 | 36 | function code() { 37 | // Block three 38 | import { waldo } from 'fred'; 39 | import { plugh } from 'xyzzy'; 40 | // Block four 41 | import { thud } from 'wibble'; 42 | import { wobble } from 'wubble'; 43 | } 44 | 45 | // Insert here 46 | const insert = 'placeholder'; 47 | ``` 48 | 49 | ```js 50 | // Block one 51 | import { foo } from 'bar'; 52 | import { baz } from 'qux'; 53 | import * as food from 'quux'; 54 | import { corge } from 'grault'; 55 | // Block two 56 | import { quux } from 'corge'; 57 | import { grault } from 'garply'; 58 | 59 | function code() { 60 | // Block three 61 | import { waldo } from 'fred'; 62 | import { plugh } from 'xyzzy'; 63 | // Block four 64 | import { thud } from 'wibble'; 65 | import { wobble } from 'wubble'; 66 | } 67 | 68 | // Insert here 69 | 70 | // new block 71 | import { foo } from 'bar'; 72 | import { baz } from 'qux'; 73 | import * as food from 'quux'; 74 | import { corge } from 'grault'; 75 | // new block 76 | import { quux } from 'corge'; 77 | import { grault } from 'garply'; 78 | // new block 79 | import { waldo } from 'fred'; 80 | import { plugh } from 'xyzzy'; 81 | // new block 82 | import { thud } from 'wibble'; 83 | import { wobble } from 'wubble'; 84 | ``` 85 | -------------------------------------------------------------------------------- /.grit/patterns/yaml/actions_runner.md: -------------------------------------------------------------------------------- 1 | # GitHub Actions Runner 2 | 3 | Standardize on a GitHub Actions runner. 4 | 5 | ```grit 6 | language yaml 7 | 8 | `runs-on: $runner` where { 9 | $runner <: or { 10 | r"ubuntu.+" => `nscloud-ubuntu-22.04-amd64-4x16`, 11 | r"macos.+" => `nscloud-macos-4x16` 12 | } 13 | } 14 | ``` 15 | 16 | ## Examples 17 | 18 | ### Ubuntu 19 | 20 | Before: 21 | 22 | ```yaml 23 | name: grit-check 24 | 25 | on: 26 | push: 27 | branches: 28 | - main 29 | pull_request: 30 | branches: 31 | - '*' 32 | 33 | jobs: 34 | run: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Check out code 38 | uses: actions/checkout@v4 39 | - name: grit-check 40 | uses: getgrit/github-action-check@v0 41 | ``` 42 | 43 | After: 44 | 45 | ```yaml 46 | name: grit-check 47 | 48 | on: 49 | push: 50 | branches: 51 | - main 52 | pull_request: 53 | branches: 54 | - '*' 55 | 56 | jobs: 57 | run: 58 | runs-on: nscloud-ubuntu-22.04-amd64-4x16 59 | steps: 60 | - name: Check out code 61 | uses: actions/checkout@v4 62 | - name: grit-check 63 | uses: getgrit/github-action-check@v0 64 | ``` 65 | 66 | ### macOS 67 | 68 | Namespace Cloud also supports macOS runners. 69 | 70 | ```yaml 71 | name: grit-check 72 | 73 | jobs: 74 | run: 75 | runs-on: macos-latest 76 | steps: 77 | - name: Check out code 78 | uses: actions/checkout@v4 79 | - name: grit-check 80 | uses: getgrit/github-action-check@v0 81 | ``` 82 | 83 | ```yaml 84 | name: grit-check 85 | 86 | jobs: 87 | run: 88 | runs-on: nscloud-macos-4x16 89 | steps: 90 | - name: Check out code 91 | uses: actions/checkout@v4 92 | - name: grit-check 93 | uses: getgrit/github-action-check@v0 94 | ``` 95 | -------------------------------------------------------------------------------- /.grit/patterns/yaml/yaml.grit: -------------------------------------------------------------------------------- 1 | language yaml 2 | 3 | // All core stdlib functions can be done here 4 | private pattern before_each_file_stdlib() { file() } 5 | 6 | private pattern after_each_file_stdlib() { after_each_file_global_rewrites() } 7 | 8 | // These could be redefined in the future (not presently supported) 9 | pattern before_each_file() { before_each_file_stdlib() } 10 | 11 | pattern after_each_file() { after_each_file_stdlib() } 12 | -------------------------------------------------------------------------------- /.grit/workflows/styled.ts: -------------------------------------------------------------------------------- 1 | import * as grit from '@getgrit/api'; 2 | 3 | export async function execute(options: grit.WorkflowOptions) { 4 | const transformResult = await grit.stdlib.transform( 5 | { 6 | objective: `You are an expert software engineer working on a migration from styled-components to Tailwind CSS. 7 | Given a styled component, you must migrate it a simple component with appropriate Tailwind classes.`, 8 | principles: ['Use the twMerge library to conditionally combine classes.'], 9 | model: 'slow', 10 | query: 'or { js"styled.$_`$_`", js"styled($_)`$_`" }', 11 | examples: [ 12 | { 13 | input: `const StyledComponent = styled(({ backgroundColor, ...otherProps }) => ( 14 | 15 | ))\` 16 | position: relative; 17 | background-color: \${({ backgroundColor }) => backgroundColor}; 18 | \`;`, 19 | replacements: [ 20 | `({backgroundColor, ...otherProps}) => { 21 | const bgColorClass = backgroundColor ? \`bg-\${backgroundColor}\` : ''; 22 | const className = twMerge(\`relative \${bgColorClass}\`); 23 | return ( 24 | 25 | ); 26 | }`, 27 | ], 28 | }, 29 | ], 30 | }, 31 | {}, 32 | ); 33 | if (!transformResult.success) return transformResult; 34 | await grit.stdlib.apply( 35 | { 36 | query: `js"twMerge" as $mg where { $mg <: ensure_import_from(source=js"'tailwind-merge'") }`, 37 | }, 38 | { paths: transformResult.paths }, 39 | ); 40 | await grit.stdlib.apply( 41 | { 42 | query: `js"import $_ from 'styled-components'" => .`, 43 | }, 44 | { paths: transformResult.paths }, 45 | ); 46 | return { 47 | success: true, 48 | message: `Successfully migrated ${transformResult.paths.length} files.`, 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "endOfLine": "auto", 4 | "singleQuote": true, 5 | "jsxSingleQuote": true, 6 | "tabWidth": 2, 7 | "printWidth": 100, 8 | "bracketSpacing": true, 9 | "overrides": [ 10 | { 11 | "files": "*.mdoc", 12 | "options": { 13 | "parser": "markdown" 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor Guide 2 | 3 | ## Getting started 4 | 5 | Make sure you have the Grit CLI installed: 6 | 7 | ```sh 8 | npm install -g @getgrit/launcher 9 | ``` 10 | 11 | To propose changes, fork this repository and open a pull request. 12 | 13 | ## Adding a new pattern 14 | 15 | All patterns require at least one sample validating the functionality. As a result, the best way to develop a 16 | GritQL pattern is often by starting with before and after sample(s) of the code to be transformed. You can 17 | iterate in the [GritQL studio](https://app.grit.io/studio) to develop your pattern. 18 | 19 | Once you have a pattern, you can add it to the repository by creating a new `.md` file in the `.grit/patterns` 20 | directory. The file name must be the snake-cased name of the pattern. Kebab case/dashes in `.md` files are not allowed by the GritQL parser. Keep the file name short and 21 | descriptive, and add a concise description as well as any relevant tags. All PRs must contain at least 22 | one sample of before/after code with a descriptive name. 23 | 24 | ## Testing 25 | 26 | Samples can be tested locally using the Grit CLI. 27 | 28 | ```sh 29 | grit patterns test 30 | ``` 31 | 32 | Once you have your sample(s) passing locally, creating a PR will automatically trigger a CI build running the same tests in a range of environments. 33 | 34 | ## GritQL conventions 35 | 36 | When writing GritQL patterns, follow the conventions in the [GritQL conventions](./grit/patterns/gritql_conventions) directory. 37 | 38 | You can confirm you're following the conventions by running the following command: 39 | 40 | ```sh 41 | grit check 42 | ``` 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grit Patterns 2 | 3 | This repository contains curated Grit patterns. 4 | 5 | ## License 6 | 7 | This repository is licensed exclusively for usage with the Grit tool. 8 | All rights reserved. 9 | 10 | ## Contributing 11 | 12 | We welcome contributions from the community. Please see the [contributing guide](CONTRIBUTING.md) for more information. 13 | -------------------------------------------------------------------------------- /old_patterns/NonTrivialMath.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Non-trivial math 3 | --- 4 | 5 | ```grit 6 | or { 7 | // ds-math 8 | or { `wmul($_)`, `wdiv($_)`, `rmul($_)`, `rdiv($_)`, `pow($_)` }, 9 | 10 | // modulus 11 | `$_ % $_` 12 | } 13 | 14 | 15 | ``` 16 | 17 | ## wmul use 18 | 19 | ```Solidity 20 | // SPDX-License-Identifier: MIT 21 | // compiler version must be greater than or equal to 0.8.13 and less than 0.9.0 22 | pragma solidity ^0.8.9; 23 | 24 | contract HelloWorld is UUPSUpgradeable, Another { 25 | string public greet = "Hello World!"; 26 | 27 | function foo(string memory _greet) public { 28 | greet = wmul(1, 2); 29 | } 30 | } 31 | 32 | ``` 33 | 34 | ```Solidity 35 | // SPDX-License-Identifier: MIT 36 | // compiler version must be greater than or equal to 0.8.13 and less than 0.9.0 37 | pragma solidity ^0.8.9; 38 | 39 | contract HelloWorld is UUPSUpgradeable, Another { 40 | string public greet = "Hello World!"; 41 | 42 | function foo(string memory _greet) public { 43 | greet = wmul(1, 2); 44 | } 45 | } 46 | 47 | ``` 48 | 49 | ## Modulus 50 | 51 | ```Solidity 52 | // SPDX-License-Identifier: MIT 53 | // compiler version must be greater than or equal to 0.8.13 and less than 0.9.0 54 | pragma solidity ^0.8.9; 55 | 56 | contract HelloWorld is UUPSUpgradeable, Another { 57 | string public value = 4; 58 | 59 | function foo(string memory _greet) public { 60 | value = 10 % 2; 61 | } 62 | } 63 | 64 | ``` 65 | 66 | ```Solidity 67 | // SPDX-License-Identifier: MIT 68 | // compiler version must be greater than or equal to 0.8.13 and less than 0.9.0 69 | pragma solidity ^0.8.9; 70 | 71 | contract HelloWorld is UUPSUpgradeable, Another { 72 | string public value = 4; 73 | 74 | function foo(string memory _greet) public { 75 | value = 10 % 2; 76 | } 77 | } 78 | 79 | ``` 80 | -------------------------------------------------------------------------------- /old_patterns/ReentrancyBeforeAndAfter.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Reentrancy, assignments both before and after 3 | --- 4 | 5 | A transfer with member assignments both before and after the transfer. 6 | 7 | See case 2 here: https://github.com/runtimeverification/amp/issues/39#issuecomment-1137314683 8 | 9 | tags: #reentrancy, #vulnerability 10 | 11 | ```grit 12 | [ 13 | ..., 14 | `this.$_ = $_` as $memberAccessBefore, 15 | ..., 16 | EtherTransfer($amount) as $theTransfer, 17 | ..., 18 | `this.$_ = $_`as $memberAccessAfter, 19 | ... 20 | ] 21 | ``` 22 | 23 | ## Example 24 | 25 | ```Solidity 26 | function claim( 27 | uint256 numPasses, 28 | uint256 amount, 29 | uint256 mpIndex, 30 | bytes32[] calldata merkleProof 31 | ) external payable { 32 | require(isValidClaim(numPasses,amount,mpIndex,merkleProof)); 33 | 34 | //return any excess funds to sender if overpaid 35 | uint256 excessPayment = msg.value.sub(numPasses.mul(mintPasses[mpIndex].mintPrice)); 36 | (bool returnExcessStatus, ) = _msgSender().call{value: excessPayment}(""); 37 | 38 | mintPasses[mpIndex].claimedMPs[msg.sender] = mintPasses[mpIndex].claimedMPs[msg.sender].add(numPasses); 39 | _mint(msg.sender, mpIndex, numPasses, ""); 40 | emit Claimed(mpIndex, msg.sender, numPasses); 41 | } 42 | 43 | ``` 44 | -------------------------------------------------------------------------------- /old_patterns/ReentrancyLowRisk.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Reentrancy, not last line 3 | --- 4 | 5 | Member assignemnt just before the transfer, but transfer not on the last line 6 | 7 | See case 3 here: https://github.com/runtimeverification/amp/issues/39#issuecomment-1137314683 8 | 9 | tags: #reentrancy, #vulnerability, #lowrisk 10 | 11 | ```grit 12 | and { 13 | [ 14 | ..., 15 | `this.$_ = $_` as $memberAccessBefore, 16 | ..., 17 | EtherTransfer($amount) as $theTransfer, 18 | $anotherLine 19 | ], 20 | // just a guard so only the ReentrancyBeforeAndAfter matches 21 | not [ 22 | ..., 23 | `this.$_ = $_`, 24 | ..., 25 | EtherTransfer($amount) as $theTransfer, 26 | ..., 27 | `this.$_ = $_`, 28 | ... 29 | ] 30 | } 31 | ``` 32 | 33 | ## Example 34 | 35 | ```Solidity 36 | function claim( 37 | uint256 numPasses, 38 | uint256 amount, 39 | uint256 mpIndex, 40 | bytes32[] calldata merkleProof 41 | ) external payable { 42 | require(isValidClaim(numPasses,amount,mpIndex,merkleProof)); 43 | 44 | //return any excess funds to sender if overpaid 45 | uint256 excessPayment = msg.value.sub(numPasses.mul(mintPasses[mpIndex].mintPrice)); 46 | (bool returnExcessStatus, ) = _msgSender().call{value: excessPayment}(""); 47 | 48 | mintPasses[mpIndex].claimedMPs[msg.sender] = mintPasses[mpIndex].claimedMPs[msg.sender].add(numPasses); 49 | _mint(msg.sender, mpIndex, numPasses, ""); 50 | emit Claimed(mpIndex, msg.sender, numPasses); 51 | } 52 | 53 | ``` 54 | -------------------------------------------------------------------------------- /old_patterns/ReentrancyNoBefore.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Reentrancy, no assignment before 3 | --- 4 | 5 | No field assignment before a transfer. 6 | 7 | See case 1 here: https://github.com/runtimeverification/amp/issues/39#issuecomment-1137314683 8 | 9 | tags: #reentrancy, #vulnerability 10 | 11 | ```grit 12 | and { 13 | [ ... contains EtherTransfer($amount) ], 14 | not [ 15 | ..., 16 | `this.$x = $y`, 17 | ..., 18 | EtherTransfer($amount), 19 | ... 20 | ] 21 | } 22 | ``` 23 | 24 | ## Example 25 | 26 | ```Solidity 27 | function claim( 28 | uint256 numPasses, 29 | uint256 amount, 30 | uint256 mpIndex, 31 | bytes32[] calldata merkleProof 32 | ) external payable { 33 | require(isValidClaim(numPasses,amount,mpIndex,merkleProof)); 34 | 35 | //return any excess funds to sender if overpaid 36 | uint256 excessPayment = msg.value.sub(numPasses.mul(mintPasses[mpIndex].mintPrice)); 37 | (bool returnExcessStatus, ) = _msgSender().call{value: excessPayment}(""); 38 | 39 | mintPasses[mpIndex].claimedMPs[msg.sender] = mintPasses[mpIndex].claimedMPs[msg.sender].add(numPasses); 40 | _mint(msg.sender, mpIndex, numPasses, ""); 41 | emit Claimed(mpIndex, msg.sender, numPasses); 42 | } 43 | 44 | ``` 45 | 46 | ```Solidity 47 | function claim( 48 | uint256 numPasses, 49 | uint256 amount, 50 | uint256 mpIndex, 51 | bytes32[] calldata merkleProof 52 | ) external payable { 53 | require(isValidClaim(numPasses,amount,mpIndex,merkleProof)); 54 | 55 | //return any excess funds to sender if overpaid 56 | uint256 excessPayment = msg.value.sub(numPasses.mul(mintPasses[mpIndex].mintPrice)); 57 | (bool returnExcessStatus, ) = _msgSender().call{value: excessPayment}(""); 58 | 59 | mintPasses[mpIndex].claimedMPs[msg.sender] = mintPasses[mpIndex].claimedMPs[msg.sender].add(numPasses); 60 | _mint(msg.sender, mpIndex, numPasses, ""); 61 | emit Claimed(mpIndex, msg.sender, numPasses); 62 | } 63 | 64 | ``` 65 | -------------------------------------------------------------------------------- /old_patterns/shared.grit: -------------------------------------------------------------------------------- 1 | pattern EtherTransfer($amount) { 2 | bubble or { 3 | `$sender.call{value: $amount}($_)`, 4 | `$sender.call.value($amount)($_)`, 5 | `$call($amount)` where { 6 | $call <: MemberAccess(_, $address, _, _, _, _, _, $functionName, _, _, null), 7 | $functionName <: r".*transfer.*" 8 | } 9 | } 10 | } 11 | 12 | pattern Loop($body) { 13 | or { 14 | `while($_) { $body; }`, 15 | `do { $body; } while ($_)`, 16 | `for ($_; $_; $_) { $body; }` 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /old_patterns/system.grit: -------------------------------------------------------------------------------- 1 | 2 | // Required by Unhack rewriter, do not delete 3 | 4 | pattern PreludeRun() { $_ } 5 | 6 | pattern ConclusionRun() { $_ } 7 | -------------------------------------------------------------------------------- /ops/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script is meant for setting up a working devcontainer 4 | 5 | # Install the Grit CLI 6 | curl -fsSL https://docs.grit.io/install | bash 7 | source ~/.bashrc 8 | 9 | -------------------------------------------------------------------------------- /ops/set_access_token.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Retrieve client ID and secret from environment variables 4 | CLIENT_ID=$(printenv API_CLIENT_ID) 5 | CLIENT_SECRET=$(printenv API_CLIENT_SECRET) 6 | 7 | # Check if CLIENT_ID and CLIENT_SECRET are set 8 | if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then 9 | echo "API_CLIENT_ID or API_CLIENT_SECRET is not set." 10 | exit 0 11 | fi 12 | 13 | # Set the Auth0 Tenant Domain and API Audience 14 | AUTH0_TENANT_DOMAIN="auth0.grit.io" # replace with your domain 15 | AUTH0_API_AUDIENCE="https://api2.grit.io" # replace with your API audience 16 | 17 | # Making a POST request to get the token 18 | RESPONSE=$(curl -s -X POST "https://$AUTH0_TENANT_DOMAIN/oauth/token" \ 19 | -H "Content-Type: application/json" \ 20 | -d "{\"client_id\":\"$CLIENT_ID\", \"client_secret\":\"$CLIENT_SECRET\", \"audience\":\"$AUTH0_API_AUDIENCE\", \"grant_type\":\"client_credentials\"}") 21 | 22 | # Extracting the access token from the response 23 | ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.access_token') 24 | 25 | # Check if ACCESS_TOKEN is retrieved 26 | if [ -z "$ACCESS_TOKEN" ]; then 27 | echo "Failed to get access token." 28 | exit 1 29 | else 30 | echo "GRIT_AUTH_TOKEN is set." 31 | # Echo the access token into GITHUB_ENV 32 | echo "GRIT_AUTH_TOKEN=$ACCESS_TOKEN" >> $GITHUB_ENV 33 | fi 34 | -------------------------------------------------------------------------------- /samples/styled.in.tsx: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | 3 | export const Button = styled.a<{ $primary?: boolean }>` 4 | background: transparent; 5 | border-radius: 3px; 6 | 7 | &:hover { 8 | filter: brightness(0.85); 9 | } 10 | 11 | ${(props) => 12 | props.$primary && 13 | css` 14 | background: red; 15 | color: black; 16 | `} 17 | `; 18 | -------------------------------------------------------------------------------- /wip/AddTypeScriptTypes.md: -------------------------------------------------------------------------------- 1 | # Add TypeScript Types 2 | 3 | Replace `$TSFixMe` or `any` types with an inferred type. This should primarily be used to fix up TypeScript type annotations auto-generated during migration. 4 | 5 | 7 | 8 | ```grit 9 | language js 10 | 11 | or { 12 | ClassMethod(params=$params), 13 | FunctionDeclaration(params=$params) 14 | } where { 15 | $params <: contains bubble { 16 | or { 17 | Identifier(name="$TSFixMe") => $type, 18 | TSAnyKeyword() as $any => $type, 19 | Identifier(typeAnnotation=null => $type) 20 | } where { 21 | $type = guess(codePrefix="// fix TypeScript type declarations", fallback="any", stop=["function"]) 22 | } 23 | } 24 | } 25 | ``` 26 | 27 | Simple Function Parameters 28 | 29 | ```js 30 | function getKey(userId: $TSFixMe) { 31 | return `some-key-${userId}`; 32 | } 33 | 34 | function somethingElse(num: any) { 35 | console.log(1 + num); 36 | } 37 | ``` 38 | 39 | ```js 40 | function getKey(userId: string) { 41 | return `some-key-${userId}`; 42 | } 43 | 44 | function somethingElse(num: number) { 45 | console.log(1 + num); 46 | } 47 | ``` 48 | 49 | Class Definitions 50 | 51 | ```js 52 | class Foo { 53 | message = ""; 54 | 55 | constructor(foo: any) { 56 | this.bar = 1; 57 | this.message = foo; 58 | } 59 | } 60 | ``` 61 | 62 | ```js 63 | class Foo { 64 | message = ""; 65 | 66 | constructor(foo: string) { 67 | this.bar = 1; 68 | this.message = foo; 69 | } 70 | } 71 | ``` 72 | 73 | Function with no types 74 | 75 | ```js 76 | function getKey(userId) { 77 | return `some-key-${userId}`; 78 | } 79 | ``` 80 | 81 | ```js 82 | function getKey(userId: string) { 83 | return `some-key-${userId}`; 84 | } 85 | ``` 86 | -------------------------------------------------------------------------------- /wip/RemoveUnusedImports.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: RemoveUnusedImports 3 | --- 4 | 5 | Remove imports not used in the $target content. 6 | 7 | tags: #imports, #lint, #deadcode, #linter 8 | 9 | ```grit 10 | language js 11 | 12 | RemoveUnusedImports($program) 13 | ``` 14 | 15 | ## grit/example.js 16 | 17 | ```js 18 | import { initTRPC, TRPCError } from '@trpc/server'; 19 | import * as Sentry from '@sentry/remix'; 20 | import * as BadGlobal from 'nowhere'; 21 | import { foo as alias } from 'somewhere'; 22 | import { Context } from './trpcContext.server'; 23 | import { db } from '../db'; 24 | import { neither, used, nor as knife } from 'knowhere'; 25 | 26 | db.do(); 27 | 28 | Sentry.hello(); 29 | initTRPC.demo(); 30 | alias.called(); 31 | ``` 32 | 33 | ```js 34 | import { initTRPC } from '@trpc/server'; 35 | import * as Sentry from '@sentry/remix'; 36 | import { foo as alias } from 'somewhere'; 37 | import { db } from '../db'; 38 | 39 | db.do(); 40 | 41 | Sentry.hello(); 42 | initTRPC.demo(); 43 | alias.called(); 44 | ``` 45 | -------------------------------------------------------------------------------- /wip/grit_distribute_or.md: -------------------------------------------------------------------------------- 1 | # Distribute OR clauses 2 | 3 | When you have a query that has multiple OR clauses, you can distribute the OR clauses to make the query more readable. This is especially useful when you have a long list of OR clauses. 4 | 5 | ```grit 6 | language grit 7 | 8 | `foo` => `bar` 9 | ``` 10 | 11 | ## Example 12 | 13 | ``` 14 | engine marzano(0.1) 15 | language python 16 | 17 | `hashlib.new($params)` 18 | ``` 19 | ``` 20 | engine marzano(0.1) 21 | language python 22 | 23 | `hashlib.new($params)` 24 | ``` -------------------------------------------------------------------------------- /wip/grit_example_labels.md: -------------------------------------------------------------------------------- 1 | # Label good and bad outside of the examples 2 | 3 | While it is tempting to put "# GOOD" and "# BAD" comments inside examples, the Grit testing framework makes it confusing by transforming the "bad" examples into "good" examples without modifying the comments. Instead, put the "good" and "bad" labels outside of the examples. 4 | 5 | ```grit 6 | language markdown 7 | 8 | file($body, $name) where { 9 | $name <: includes ".grit/patterns", 10 | $name <: not includes "example_labels", 11 | $body <: contains bubble code_span() as $code where { 12 | $code <: r"(?s)(?:.+)(GOOD|BAD)(?:.+)"($label), 13 | $label => ., 14 | $code => `$label\n$code`, 15 | } 16 | } 17 | ``` 18 | 19 | ### Example 20 | 21 | Bad example, the "good" labels are inside the examples: 22 | ```md 23 | // @filename: .grit/patterns/gritql_conventions/bad_sample.md 24 | `# GOOD This is some code sample more stuff` 25 | ``` 26 | ```md 27 | // @filename: .grit/patterns/gritql_conventions/bad_sample.md 28 | GOOD 29 | `# This is some code sample more stuff` 30 | ``` 31 | 32 | ### Multi-Example 33 | 34 | Bad example, the "good" labels are inside the examples: 35 | ````md 36 | // @filename: .grit/patterns/gritql_conventions/bad_sample.md 37 | Yada yaa 38 | ``` 39 | # GOOD 40 | source code here 41 | ``` 42 | ```` 43 | 44 | Corrected example: 45 | ````md 46 | // @filename: .grit/patterns/gritql_conventions/bad_sample.md 47 | Yada yaa 48 | GOOD 49 | ``` 50 | # 51 | source code here 52 | ``` 53 | ```` 54 | 55 | ### Counter-example 56 | 57 | Good example, the "good" labels are outside of the examples: 58 | 59 | ```md 60 | // @filename: .grit/patterns/gritql_conventions/good_sample.md 61 | # Good 62 | `This is some code sample` 63 | # Bad 64 | `This is some other code sample` 65 | ``` 66 | ```md 67 | // @filename: .grit/patterns/gritql_conventions/good_sample.md 68 | # Good 69 | `This is some code sample` 70 | # Bad 71 | `This is some other code sample` 72 | ``` 73 | --------------------------------------------------------------------------------