@ttsc/lint 0.13.1 → 0.14.0-dev.20260528.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +948 -69
- package/lib/defaultFormat.d.ts +9 -6
- package/lib/defaultFormat.js +9 -6
- package/lib/defaultFormat.js.map +1 -1
- package/lib/index.d.ts +46 -0
- package/lib/index.js +35 -10
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +12 -6
- package/lib/structures/{ITtscLintFormatConfig.d.ts → ITtscLintFormat.d.ts} +2 -2
- package/lib/structures/{TtscLintRuleMap.js → ITtscLintFormat.js} +1 -1
- package/lib/structures/ITtscLintFormat.js.map +1 -0
- package/lib/structures/TtscLintRuleSetting.d.ts +49 -0
- package/lib/structures/{TtscLintRuleOptions.js → TtscLintRuleSetting.js} +1 -1
- package/lib/structures/TtscLintRuleSetting.js.map +1 -0
- package/lib/structures/index.d.ts +3 -4
- package/lib/structures/index.js +3 -4
- package/lib/structures/index.js.map +1 -1
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +104 -0
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js +14 -0
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +79 -0
- package/lib/structures/rules/ITtscLintBoundariesRules.js +3 -0
- package/lib/structures/rules/ITtscLintBoundariesRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintContributorRules.d.ts +17 -0
- package/lib/structures/rules/ITtscLintContributorRules.js +3 -0
- package/lib/structures/rules/ITtscLintContributorRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintCoreRules.d.ts +1257 -0
- package/lib/structures/{ITtscLintFormatConfig.js → rules/ITtscLintCoreRules.js} +1 -1
- package/lib/structures/rules/ITtscLintCoreRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintCypressRuleOptions.d.ts +17 -0
- package/lib/structures/rules/ITtscLintCypressRuleOptions.js +10 -0
- package/lib/structures/rules/ITtscLintCypressRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintCypressRules.d.ts +129 -0
- package/lib/structures/rules/ITtscLintCypressRules.js +3 -0
- package/lib/structures/rules/ITtscLintCypressRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintFunctionalRuleOptions.d.ts +157 -0
- package/lib/structures/rules/ITtscLintFunctionalRuleOptions.js +13 -0
- package/lib/structures/rules/ITtscLintFunctionalRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintFunctionalRules.d.ts +167 -0
- package/lib/structures/rules/ITtscLintFunctionalRules.js +3 -0
- package/lib/structures/rules/ITtscLintFunctionalRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintJestRules.d.ts +201 -0
- package/lib/structures/rules/ITtscLintJestRules.js +3 -0
- package/lib/structures/rules/ITtscLintJestRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintJsdocRules.d.ts +140 -0
- package/lib/structures/rules/ITtscLintJsdocRules.js +3 -0
- package/lib/structures/rules/ITtscLintJsdocRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintJsxA11yRules.d.ts +302 -0
- package/lib/structures/rules/ITtscLintJsxA11yRules.js +3 -0
- package/lib/structures/rules/ITtscLintJsxA11yRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintNextjsRules.d.ts +185 -0
- package/lib/structures/rules/ITtscLintNextjsRules.js +3 -0
- package/lib/structures/rules/ITtscLintNextjsRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintPlaywrightRules.d.ts +330 -0
- package/lib/structures/rules/ITtscLintPlaywrightRules.js +3 -0
- package/lib/structures/rules/ITtscLintPlaywrightRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintPromiseRules.d.ts +188 -0
- package/lib/structures/rules/ITtscLintPromiseRules.js +3 -0
- package/lib/structures/rules/ITtscLintPromiseRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintReactPerfRuleOptions.d.ts +18 -0
- package/lib/structures/rules/ITtscLintReactPerfRuleOptions.js +8 -0
- package/lib/structures/rules/ITtscLintReactPerfRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintReactPerfRules.d.ts +51 -0
- package/lib/structures/rules/ITtscLintReactPerfRules.js +3 -0
- package/lib/structures/rules/ITtscLintReactPerfRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintReactRuleOptions.d.ts +39 -0
- package/lib/structures/rules/ITtscLintReactRuleOptions.js +12 -0
- package/lib/structures/rules/ITtscLintReactRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintReactRules.d.ts +289 -0
- package/lib/structures/rules/ITtscLintReactRules.js +3 -0
- package/lib/structures/rules/ITtscLintReactRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintRegexpRules.d.ts +190 -0
- package/lib/structures/rules/ITtscLintRegexpRules.js +3 -0
- package/lib/structures/rules/ITtscLintRegexpRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +66 -0
- package/lib/structures/rules/ITtscLintRuleOptionsMap.js +3 -0
- package/lib/structures/rules/ITtscLintRuleOptionsMap.js.map +1 -0
- package/lib/structures/rules/ITtscLintRules.d.ts +55 -0
- package/lib/structures/{TtscLintRule.js → rules/ITtscLintRules.js} +1 -1
- package/lib/structures/rules/ITtscLintRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintSecurityRules.d.ts +148 -0
- package/lib/structures/rules/ITtscLintSecurityRules.js +3 -0
- package/lib/structures/rules/ITtscLintSecurityRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintSolidRules.d.ts +181 -0
- package/lib/structures/rules/ITtscLintSolidRules.js +3 -0
- package/lib/structures/rules/ITtscLintSolidRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintStorybookRuleOptions.d.ts +21 -0
- package/lib/structures/rules/ITtscLintStorybookRuleOptions.js +9 -0
- package/lib/structures/rules/ITtscLintStorybookRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintStorybookRules.d.ts +174 -0
- package/lib/structures/rules/ITtscLintStorybookRules.js +3 -0
- package/lib/structures/rules/ITtscLintStorybookRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintTanstackQueryRules.d.ts +94 -0
- package/lib/structures/rules/ITtscLintTanstackQueryRules.js +3 -0
- package/lib/structures/rules/ITtscLintTanstackQueryRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.d.ts +22 -0
- package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.js +10 -0
- package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintTestingLibraryRules.d.ts +310 -0
- package/lib/structures/rules/ITtscLintTestingLibraryRules.js +3 -0
- package/lib/structures/rules/ITtscLintTestingLibraryRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +1132 -0
- package/lib/structures/rules/ITtscLintTypeScriptRules.js +3 -0
- package/lib/structures/rules/ITtscLintTypeScriptRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintUnicornRules.d.ts +997 -0
- package/lib/structures/rules/ITtscLintUnicornRules.js +3 -0
- package/lib/structures/rules/ITtscLintUnicornRules.js.map +1 -0
- package/lib/structures/rules/ITtscLintVitestRules.d.ts +141 -0
- package/lib/structures/rules/ITtscLintVitestRules.js +3 -0
- package/lib/structures/rules/ITtscLintVitestRules.js.map +1 -0
- package/lib/structures/rules/index.d.ts +31 -0
- package/lib/structures/rules/index.js +48 -0
- package/lib/structures/rules/index.js.map +1 -0
- package/linthost/ast_helpers.go +30 -5
- package/linthost/config.go +48 -17
- package/linthost/config_format.go +9 -9
- package/linthost/directives.go +67 -25
- package/linthost/dispatch.go +14 -3
- package/linthost/engine.go +152 -31
- package/linthost/fix.go +2 -2
- package/linthost/lsp.go +747 -0
- package/linthost/print_dispatch.go +2 -2
- package/linthost/print_nodes_function.go +2 -2
- package/linthost/print_nodes_object.go +1 -1
- package/linthost/rule_names.go +14 -0
- package/linthost/rules_arrays.go +2 -2
- package/linthost/rules_boundaries.go +532 -0
- package/linthost/rules_camelcase.go +133 -0
- package/linthost/rules_complexity.go +94 -0
- package/linthost/rules_consistent_return.go +130 -0
- package/linthost/rules_console.go +1 -1
- package/linthost/rules_core_extra.go +739 -0
- package/linthost/rules_curly.go +70 -0
- package/linthost/rules_cypress.go +608 -0
- package/linthost/rules_debugger.go +2 -2
- package/linthost/rules_default_case.go +39 -0
- package/linthost/rules_default_case_last.go +46 -0
- package/linthost/rules_dupes.go +3 -3
- package/linthost/rules_empty.go +3 -3
- package/linthost/rules_escape.go +44 -24
- package/linthost/rules_eval.go +2 -2
- package/linthost/rules_finally.go +2 -2
- package/linthost/rules_format_jsdoc.go +1 -1
- package/linthost/rules_format_print_width.go +9 -9
- package/linthost/rules_format_quotes.go +1 -1
- package/linthost/rules_format_semi.go +1 -1
- package/linthost/rules_format_sort_imports.go +1 -1
- package/linthost/rules_format_trailing_comma.go +1 -1
- package/linthost/rules_functional.go +967 -0
- package/linthost/rules_gap.go +24 -45
- package/linthost/rules_grouped_accessor_pairs.go +87 -0
- package/linthost/rules_guard_for_in.go +198 -0
- package/linthost/rules_id_length.go +77 -0
- package/linthost/rules_imports.go +70 -2
- package/linthost/rules_init_declarations.go +53 -0
- package/linthost/rules_jest.go +618 -0
- package/linthost/rules_jsdoc.go +502 -0
- package/linthost/rules_jsx_a11y.go +1264 -0
- package/linthost/rules_logic.go +12 -12
- package/linthost/rules_loops.go +1 -1
- package/linthost/rules_max_classes_per_file.go +58 -0
- package/linthost/rules_max_depth.go +83 -0
- package/linthost/rules_max_lines.go +101 -0
- package/linthost/rules_max_lines_per_function.go +57 -0
- package/linthost/rules_max_nested_callbacks.go +61 -0
- package/linthost/rules_max_params.go +49 -0
- package/linthost/rules_max_statements.go +59 -0
- package/linthost/rules_misc.go +1 -1
- package/linthost/rules_nextjs.go +697 -0
- package/linthost/rules_no_else_return.go +75 -0
- package/linthost/rules_no_empty_named_blocks.go +91 -0
- package/linthost/rules_no_extend_native.go +51 -0
- package/linthost/rules_no_import_assign.go +140 -0
- package/linthost/rules_no_invalid_this.go +52 -0
- package/linthost/rules_no_loop_func.go +85 -0
- package/linthost/rules_no_magic_numbers.go +123 -0
- package/linthost/rules_no_mixed_operators.go +80 -0
- package/linthost/rules_no_param_reassign.go +98 -0
- package/linthost/rules_no_redeclare.go +270 -0
- package/linthost/rules_no_restricted_imports.go +105 -0
- package/linthost/rules_no_restricted_syntax.go +49 -0
- package/linthost/rules_no_shadow.go +147 -0
- package/linthost/rules_no_unreachable.go +77 -0
- package/linthost/rules_no_useless_assignment.go +120 -0
- package/linthost/rules_no_useless_constructor.go +245 -0
- package/linthost/rules_no_useless_return.go +51 -0
- package/linthost/rules_params.go +1 -1
- package/linthost/rules_playwright.go +788 -0
- package/linthost/rules_prefer_arrow_callback.go +124 -0
- package/linthost/rules_prefer_destructuring.go +98 -0
- package/linthost/rules_prefer_named_capture_group.go +129 -0
- package/linthost/rules_prefer_rest_params.go +94 -0
- package/linthost/rules_problems.go +41 -30
- package/linthost/rules_promise.go +1128 -73
- package/linthost/rules_protos.go +2 -2
- package/linthost/rules_react.go +773 -0
- package/linthost/rules_react_extras.go +227 -0
- package/linthost/rules_react_hooks.go +848 -0
- package/linthost/rules_react_perf.go +293 -0
- package/linthost/rules_react_refresh.go +583 -0
- package/linthost/rules_regexp.go +410 -0
- package/linthost/rules_security.go +830 -0
- package/linthost/rules_self.go +2 -2
- package/linthost/rules_solid.go +1073 -0
- package/linthost/rules_sort_imports.go +69 -0
- package/linthost/rules_sort_keys.go +134 -0
- package/linthost/rules_storybook.go +1176 -0
- package/linthost/rules_strings.go +4 -4
- package/linthost/rules_suggestions.go +38 -38
- package/linthost/rules_tanstack_query.go +844 -0
- package/linthost/rules_testing_library.go +1204 -0
- package/linthost/rules_throw.go +1 -1
- package/linthost/rules_ts.go +19 -19
- package/linthost/rules_ts_async.go +399 -0
- package/linthost/rules_ts_class_literal_property_style.go +115 -0
- package/linthost/rules_ts_consistent_type_exports.go +256 -0
- package/linthost/rules_ts_explicit_function_return_type.go +67 -0
- package/linthost/rules_ts_extra.go +155 -36
- package/linthost/rules_ts_no_base_to_string.go +218 -0
- package/linthost/rules_ts_no_confusing_void.go +72 -0
- package/linthost/rules_ts_no_deprecated.go +195 -0
- package/linthost/rules_ts_no_for_in_array.go +84 -0
- package/linthost/rules_ts_no_magic_numbers.go +124 -0
- package/linthost/rules_ts_no_meaningless_void_operator.go +84 -0
- package/linthost/rules_ts_no_misused_spread.go +104 -0
- package/linthost/rules_ts_no_redundant_type_constituents.go +113 -0
- package/linthost/rules_ts_no_restricted_types.go +62 -0
- package/linthost/rules_ts_no_unnecessary_boolean_literal_compare.go +129 -0
- package/linthost/rules_ts_no_unnecessary_condition.go +250 -0
- package/linthost/rules_ts_no_unnecessary_qualifier.go +118 -0
- package/linthost/rules_ts_no_unnecessary_template_expression.go +178 -0
- package/linthost/rules_ts_no_unnecessary_type_arguments.go +234 -0
- package/linthost/rules_ts_no_unnecessary_type_assertion.go +157 -0
- package/linthost/rules_ts_no_unsafe_argument.go +82 -0
- package/linthost/rules_ts_no_unsafe_assignment.go +102 -0
- package/linthost/rules_ts_no_unsafe_call.go +82 -0
- package/linthost/rules_ts_no_unsafe_enum_comparison.go +235 -0
- package/linthost/rules_ts_no_unsafe_member_access.go +58 -0
- package/linthost/rules_ts_no_unsafe_return.go +126 -0
- package/linthost/rules_ts_no_unsafe_unary_minus.go +81 -0
- package/linthost/rules_ts_no_useless_constructor.go +52 -0
- package/linthost/rules_ts_non_nullable_type_assertion_style.go +108 -0
- package/linthost/rules_ts_parameter_properties.go +38 -0
- package/linthost/rules_ts_prefer_find.go +178 -0
- package/linthost/rules_ts_prefer_includes.go +255 -0
- package/linthost/rules_ts_prefer_nullish_coalescing.go +104 -0
- package/linthost/rules_ts_prefer_optional_chain.go +228 -0
- package/linthost/rules_ts_prefer_promise_reject_errors.go +176 -0
- package/linthost/rules_ts_prefer_readonly.go +73 -0
- package/linthost/rules_ts_prefer_reduce_type_parameter.go +116 -0
- package/linthost/rules_ts_prefer_regexp_exec.go +122 -0
- package/linthost/rules_ts_prefer_return_this_type.go +135 -0
- package/linthost/rules_ts_prefer_string_starts_ends_with.go +360 -0
- package/linthost/rules_ts_promise_function_async.go +77 -0
- package/linthost/rules_ts_related_getter_setter_pairs.go +113 -0
- package/linthost/rules_ts_require_array_sort_compare.go +96 -0
- package/linthost/rules_ts_restrict_plus_operands.go +127 -0
- package/linthost/rules_ts_restrict_template_expressions.go +95 -0
- package/linthost/rules_ts_simple.go +387 -0
- package/linthost/rules_ts_sort_type_constituents.go +144 -0
- package/linthost/rules_ts_strict_boolean_expressions.go +157 -0
- package/linthost/rules_ts_switch_exhaustiveness_check.go +147 -0
- package/linthost/rules_ts_unbound_method.go +126 -0
- package/linthost/rules_tsdoc.go +136 -0
- package/linthost/rules_unicorn_better_regex.go +27 -0
- package/linthost/rules_unicorn_catch_error_name.go +46 -0
- package/linthost/rules_unicorn_consistent_assert.go +47 -0
- package/linthost/rules_unicorn_consistent_date_clone.go +54 -0
- package/linthost/rules_unicorn_consistent_destructuring.go +25 -0
- package/linthost/rules_unicorn_consistent_empty_array_spread.go +62 -0
- package/linthost/rules_unicorn_consistent_existence_index_check.go +80 -0
- package/linthost/rules_unicorn_consistent_function_scoping.go +25 -0
- package/linthost/rules_unicorn_consistent_template_literal_escape.go +25 -0
- package/linthost/rules_unicorn_custom_error_definition.go +138 -0
- package/linthost/rules_unicorn_empty_brace_spaces.go +73 -0
- package/linthost/rules_unicorn_error_message.go +64 -0
- package/linthost/rules_unicorn_escape_case.go +51 -0
- package/linthost/rules_unicorn_expiring_todo_comments.go +78 -0
- package/linthost/rules_unicorn_explicit_length_check.go +88 -0
- package/linthost/rules_unicorn_filename_case.go +29 -0
- package/linthost/rules_unicorn_import_style.go +28 -0
- package/linthost/rules_unicorn_isolated_functions.go +29 -0
- package/linthost/rules_unicorn_new_for_builtins.go +87 -0
- package/linthost/rules_unicorn_no_abusive_eslint_disable.go +63 -0
- package/linthost/rules_unicorn_no_accessor_recursion.go +115 -0
- package/linthost/rules_unicorn_no_anonymous_default_export.go +74 -0
- package/linthost/rules_unicorn_no_array_callback_reference.go +65 -0
- package/linthost/rules_unicorn_no_array_for_each.go +40 -0
- package/linthost/rules_unicorn_no_array_method_this_argument.go +61 -0
- package/linthost/rules_unicorn_no_array_reduce.go +41 -0
- package/linthost/rules_unicorn_no_array_reverse.go +44 -0
- package/linthost/rules_unicorn_no_array_sort.go +43 -0
- package/linthost/rules_unicorn_no_await_expression_member.go +40 -0
- package/linthost/rules_unicorn_no_await_in_promise_methods.go +69 -0
- package/linthost/rules_unicorn_no_console_spaces.go +61 -0
- package/linthost/rules_unicorn_no_document_cookie.go +41 -0
- package/linthost/rules_unicorn_no_empty_file.go +44 -0
- package/linthost/rules_unicorn_no_for_loop.go +102 -0
- package/linthost/rules_unicorn_no_hex_escape.go +42 -0
- package/linthost/rules_unicorn_no_immediate_mutation.go +90 -0
- package/linthost/rules_unicorn_no_instanceof_builtins.go +66 -0
- package/linthost/rules_unicorn_no_invalid_fetch_options.go +79 -0
- package/linthost/rules_unicorn_no_invalid_remove_event_listener.go +51 -0
- package/linthost/rules_unicorn_no_keyword_prefix.go +101 -0
- package/linthost/rules_unicorn_no_lonely_if.go +44 -0
- package/linthost/rules_unicorn_no_magic_array_flat_depth.go +51 -0
- package/linthost/rules_unicorn_no_named_default.go +54 -0
- package/linthost/rules_unicorn_no_negated_condition.go +85 -0
- package/linthost/rules_unicorn_no_negation_in_equality_check.go +50 -0
- package/linthost/rules_unicorn_no_nested_ternary.go +43 -0
- package/linthost/rules_unicorn_no_new_array.go +30 -0
- package/linthost/rules_unicorn_no_new_buffer.go +31 -0
- package/linthost/rules_unicorn_no_null.go +24 -0
- package/linthost/rules_unicorn_no_object_as_default_parameter.go +48 -0
- package/linthost/rules_unicorn_no_process_exit.go +43 -0
- package/linthost/rules_unicorn_no_single_promise_in_promise_methods.go +59 -0
- package/linthost/rules_unicorn_no_static_only_class.go +56 -0
- package/linthost/rules_unicorn_no_thenable.go +74 -0
- package/linthost/rules_unicorn_no_this_assignment.go +35 -0
- package/linthost/rules_unicorn_no_typeof_undefined.go +58 -0
- package/linthost/rules_unicorn_no_unnecessary_array_flat_depth.go +49 -0
- package/linthost/rules_unicorn_no_unnecessary_array_splice_count.go +70 -0
- package/linthost/rules_unicorn_no_unnecessary_await.go +51 -0
- package/linthost/rules_unicorn_no_unnecessary_polyfills.go +32 -0
- package/linthost/rules_unicorn_no_unnecessary_slice_end.go +48 -0
- package/linthost/rules_unicorn_no_unreadable_array_destructuring.go +115 -0
- package/linthost/rules_unicorn_no_unreadable_iife.go +50 -0
- package/linthost/rules_unicorn_no_unused_properties.go +30 -0
- package/linthost/rules_unicorn_no_useless_collection_argument.go +63 -0
- package/linthost/rules_unicorn_no_useless_error_capture_stack_trace.go +46 -0
- package/linthost/rules_unicorn_no_useless_fallback_in_spread.go +81 -0
- package/linthost/rules_unicorn_no_useless_iterator_to_array.go +72 -0
- package/linthost/rules_unicorn_no_useless_length_check.go +144 -0
- package/linthost/rules_unicorn_no_useless_promise_resolve_reject.go +81 -0
- package/linthost/rules_unicorn_no_useless_spread.go +67 -0
- package/linthost/rules_unicorn_no_useless_switch_case.go +55 -0
- package/linthost/rules_unicorn_no_useless_undefined.go +55 -0
- package/linthost/rules_unicorn_no_zero_fractions.go +52 -0
- package/linthost/rules_unicorn_number_literal_case.go +69 -0
- package/linthost/rules_unicorn_numeric_separators_style.go +75 -0
- package/linthost/rules_unicorn_prefer_add_event_listener.go +66 -0
- package/linthost/rules_unicorn_prefer_array_find.go +51 -0
- package/linthost/rules_unicorn_prefer_array_flat.go +52 -0
- package/linthost/rules_unicorn_prefer_array_flat_map.go +63 -0
- package/linthost/rules_unicorn_prefer_array_index_of.go +147 -0
- package/linthost/rules_unicorn_prefer_array_some.go +67 -0
- package/linthost/rules_unicorn_prefer_at.go +77 -0
- package/linthost/rules_unicorn_prefer_bigint_literals.go +77 -0
- package/linthost/rules_unicorn_prefer_blob_reading_methods.go +44 -0
- package/linthost/rules_unicorn_prefer_class_fields.go +93 -0
- package/linthost/rules_unicorn_prefer_classlist_toggle.go +120 -0
- package/linthost/rules_unicorn_prefer_code_point.go +49 -0
- package/linthost/rules_unicorn_prefer_date_now.go +78 -0
- package/linthost/rules_unicorn_prefer_default_parameters.go +144 -0
- package/linthost/rules_unicorn_prefer_dom_node_append.go +40 -0
- package/linthost/rules_unicorn_prefer_dom_node_dataset.go +55 -0
- package/linthost/rules_unicorn_prefer_dom_node_remove.go +41 -0
- package/linthost/rules_unicorn_prefer_dom_node_text_content.go +37 -0
- package/linthost/rules_unicorn_prefer_event_target.go +36 -0
- package/linthost/rules_unicorn_prefer_export_from.go +129 -0
- package/linthost/rules_unicorn_prefer_global_this.go +127 -0
- package/linthost/rules_unicorn_prefer_import_meta_properties.go +66 -0
- package/linthost/rules_unicorn_prefer_includes.go +149 -0
- package/linthost/rules_unicorn_prefer_json_parse_buffer.go +65 -0
- package/linthost/rules_unicorn_prefer_keyboard_event_key.go +38 -0
- package/linthost/rules_unicorn_prefer_logical_operator_over_ternary.go +48 -0
- package/linthost/rules_unicorn_prefer_math_min_max.go +60 -0
- package/linthost/rules_unicorn_prefer_math_trunc.go +62 -0
- package/linthost/rules_unicorn_prefer_modern_dom_apis.go +47 -0
- package/linthost/rules_unicorn_prefer_modern_math_apis.go +70 -0
- package/linthost/rules_unicorn_prefer_module.go +50 -0
- package/linthost/rules_unicorn_prefer_native_coercion_functions.go +114 -0
- package/linthost/rules_unicorn_prefer_negative_index.go +74 -0
- package/linthost/rules_unicorn_prefer_node_protocol.go +122 -0
- package/linthost/rules_unicorn_prefer_number_properties.go +88 -0
- package/linthost/rules_unicorn_prefer_object_from_entries.go +55 -0
- package/linthost/rules_unicorn_prefer_optional_catch_binding.go +62 -0
- package/linthost/rules_unicorn_prefer_prototype_methods.go +54 -0
- package/linthost/rules_unicorn_prefer_query_selector.go +56 -0
- package/linthost/rules_unicorn_prefer_reflect_apply.go +36 -0
- package/linthost/rules_unicorn_prefer_regexp_test_rule.go +80 -0
- package/linthost/rules_unicorn_prefer_response_static_json.go +53 -0
- package/linthost/rules_unicorn_prefer_set_has.go +42 -0
- package/linthost/rules_unicorn_prefer_set_size.go +48 -0
- package/linthost/rules_unicorn_prefer_simple_condition_first.go +34 -0
- package/linthost/rules_unicorn_prefer_single_call.go +96 -0
- package/linthost/rules_unicorn_prefer_spread.go +51 -0
- package/linthost/rules_unicorn_prefer_string_raw.go +43 -0
- package/linthost/rules_unicorn_prefer_string_replace_all.go +95 -0
- package/linthost/rules_unicorn_prefer_string_slice.go +43 -0
- package/linthost/rules_unicorn_prefer_string_starts_ends_with.go +148 -0
- package/linthost/rules_unicorn_prefer_string_trim_start_end.go +48 -0
- package/linthost/rules_unicorn_prefer_structured_clone.go +71 -0
- package/linthost/rules_unicorn_prefer_switch.go +103 -0
- package/linthost/rules_unicorn_prefer_ternary.go +71 -0
- package/linthost/rules_unicorn_prefer_top_level_await.go +72 -0
- package/linthost/rules_unicorn_prefer_type_error.go +127 -0
- package/linthost/rules_unicorn_prevent_abbreviations.go +127 -0
- package/linthost/rules_unicorn_relative_url_style.go +49 -0
- package/linthost/rules_unicorn_require_array_join_separator.go +45 -0
- package/linthost/rules_unicorn_require_module_attributes.go +54 -0
- package/linthost/rules_unicorn_require_module_specifiers.go +55 -0
- package/linthost/rules_unicorn_require_number_to_fixed_digits_argument.go +43 -0
- package/linthost/rules_unicorn_require_post_message_target_origin.go +44 -0
- package/linthost/rules_unicorn_string_content.go +27 -0
- package/linthost/rules_unicorn_switch_case_braces.go +42 -0
- package/linthost/rules_unicorn_switch_case_break_position.go +31 -0
- package/linthost/rules_unicorn_template_indent.go +28 -0
- package/linthost/rules_unicorn_text_encoding_identifier_case.go +72 -0
- package/linthost/rules_unicorn_throw_new_error.go +61 -0
- package/linthost/rules_var.go +4 -4
- package/linthost/rules_vitest.go +568 -0
- package/package.json +3 -3
- package/plugin/main.go +49 -3
- package/rule/astutil/astutil.go +10 -3
- package/rule/rule.go +12 -10
- package/src/defaultFormat.ts +11 -8
- package/src/index.ts +40 -16
- package/src/structures/ITtscLintConfig.ts +12 -6
- package/src/structures/{ITtscLintFormatConfig.ts → ITtscLintFormat.ts} +2 -2
- package/src/structures/TtscLintRuleSetting.ts +55 -0
- package/src/structures/index.ts +3 -4
- package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +128 -0
- package/src/structures/rules/ITtscLintBoundariesRules.ts +95 -0
- package/src/structures/rules/ITtscLintContributorRules.ts +21 -0
- package/src/structures/rules/ITtscLintCoreRules.ts +1406 -0
- package/src/structures/rules/ITtscLintCypressRuleOptions.ts +18 -0
- package/src/structures/rules/ITtscLintCypressRules.ts +145 -0
- package/src/structures/rules/ITtscLintFunctionalRuleOptions.ts +213 -0
- package/src/structures/rules/ITtscLintFunctionalRules.ts +202 -0
- package/src/structures/rules/ITtscLintJestRules.ts +220 -0
- package/src/structures/rules/ITtscLintJsdocRules.ts +153 -0
- package/src/structures/rules/ITtscLintJsxA11yRules.ts +339 -0
- package/src/structures/rules/ITtscLintNextjsRules.ts +206 -0
- package/src/structures/rules/ITtscLintPlaywrightRules.ts +361 -0
- package/src/structures/rules/ITtscLintPromiseRules.ts +205 -0
- package/src/structures/rules/ITtscLintReactPerfRuleOptions.ts +19 -0
- package/src/structures/rules/ITtscLintReactPerfRules.ts +55 -0
- package/src/structures/rules/ITtscLintReactRuleOptions.ts +43 -0
- package/src/structures/rules/ITtscLintReactRules.ts +321 -0
- package/src/structures/rules/ITtscLintRegexpRules.ts +212 -0
- package/src/structures/rules/ITtscLintRuleOptionsMap.ts +89 -0
- package/src/structures/rules/ITtscLintRules.ts +77 -0
- package/src/structures/rules/ITtscLintSecurityRules.ts +162 -0
- package/src/structures/rules/ITtscLintSolidRules.ts +202 -0
- package/src/structures/rules/ITtscLintStorybookRuleOptions.ts +23 -0
- package/src/structures/rules/ITtscLintStorybookRules.ts +193 -0
- package/src/structures/rules/ITtscLintTanstackQueryRules.ts +102 -0
- package/src/structures/rules/ITtscLintTestingLibraryRuleOptions.ts +24 -0
- package/src/structures/rules/ITtscLintTestingLibraryRules.ts +342 -0
- package/src/structures/rules/ITtscLintTypeScriptRules.ts +1230 -0
- package/src/structures/rules/ITtscLintUnicornRules.ts +1143 -0
- package/src/structures/rules/ITtscLintVitestRules.ts +154 -0
- package/src/structures/rules/index.ts +31 -0
- package/lib/structures/ITtscLintFormatConfig.js.map +0 -1
- package/lib/structures/TtscLintRule.d.ts +0 -17
- package/lib/structures/TtscLintRule.js.map +0 -1
- package/lib/structures/TtscLintRuleMap.d.ts +0 -46
- package/lib/structures/TtscLintRuleMap.js.map +0 -1
- package/lib/structures/TtscLintRuleOptions.d.ts +0 -137
- package/lib/structures/TtscLintRuleOptions.js.map +0 -1
- package/src/structures/TtscLintRule.ts +0 -160
- package/src/structures/TtscLintRuleMap.ts +0 -55
- package/src/structures/TtscLintRuleOptions.ts +0 -151
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// typescript/prefer-regexp-exec: prefer `re.exec(str)` over
|
|
2
|
+
// `str.match(re)` when the regex has no `g` flag. Both shapes return
|
|
3
|
+
// the same `RegExpExecArray | null` for first-match queries, but
|
|
4
|
+
// `String#match` silently switches to "every match" the moment the
|
|
5
|
+
// regex gains the `g` flag — a typo at the regex literal can change
|
|
6
|
+
// the call's return shape from `[fullMatch, ...captures]` to a flat
|
|
7
|
+
// `string[]` of matches. `RegExp#exec` is the stable form.
|
|
8
|
+
// typescript-eslint:
|
|
9
|
+
// https://typescript-eslint.io/rules/prefer-regexp-exec/
|
|
10
|
+
//
|
|
11
|
+
// Type-aware. Without a Checker the rule cannot prove the receiver of
|
|
12
|
+
// `match` is string-like, so Context.Checker == nil short-circuits each
|
|
13
|
+
// Check to a no-op the way `prefer-includes` and
|
|
14
|
+
// `prefer-string-starts-ends-with` do. The argument must be a regex
|
|
15
|
+
// literal — the AST-only baseline reads the flag suffix directly off
|
|
16
|
+
// the token text; non-literal regex arguments (a `new RegExp(...)`,
|
|
17
|
+
// a `const re = /.../` aliased through a variable) are conservatively
|
|
18
|
+
// skipped because static flag tracking would explode in scope.
|
|
19
|
+
package linthost
|
|
20
|
+
|
|
21
|
+
import (
|
|
22
|
+
"strings"
|
|
23
|
+
|
|
24
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
25
|
+
shimchecker "github.com/microsoft/typescript-go/shim/checker"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
type preferRegexpExec struct{}
|
|
29
|
+
|
|
30
|
+
func (preferRegexpExec) Name() string { return "typescript/prefer-regexp-exec" }
|
|
31
|
+
func (preferRegexpExec) NeedsTypeChecker() bool {
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
func (preferRegexpExec) Visits() []shimast.Kind {
|
|
35
|
+
return []shimast.Kind{shimast.KindCallExpression}
|
|
36
|
+
}
|
|
37
|
+
func (preferRegexpExec) Check(ctx *Context, node *shimast.Node) {
|
|
38
|
+
if ctx.Checker == nil {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
call := node.AsCallExpression()
|
|
42
|
+
if call == nil || call.Expression == nil {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
receiver, method, ok := promisePropertyAccessParts(call.Expression)
|
|
46
|
+
if !ok || method != "match" {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
if receiver == nil {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
if call.Arguments == nil || len(call.Arguments.Nodes) != 1 {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
arg := stripParens(call.Arguments.Nodes[0])
|
|
56
|
+
if arg == nil || arg.Kind != shimast.KindRegularExpressionLiteral {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
// Skip when the regex literal carries the `g` flag — `String#match`
|
|
60
|
+
// returns every match in that mode, which `RegExp#exec` cannot
|
|
61
|
+
// replicate in a single call.
|
|
62
|
+
if preferRegexpExecHasGlobalFlag(ctx.File, arg) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
if !preferRegexpExecIsString(ctx.Checker.GetTypeAtLocation(receiver)) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
ctx.Report(node, preferRegexpExecMessage)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const preferRegexpExecMessage = "Prefer `RegExp#exec(str)` over `String#match(re)` when the regex has no `g` flag — `exec` returns the same first-match shape and avoids the `g`-flag fallthrough that silently changes the return type of `match`."
|
|
72
|
+
|
|
73
|
+
// preferRegexpExecHasGlobalFlag reports whether the regex literal at
|
|
74
|
+
// `node` carries the `g` flag. The flag string follows the closing `/`
|
|
75
|
+
// of the literal (e.g. `/foo/gi` has flags `"gi"`); the scanner has
|
|
76
|
+
// already validated the token, so a textual scan of the trailing
|
|
77
|
+
// suffix is sufficient.
|
|
78
|
+
func preferRegexpExecHasGlobalFlag(file *shimast.SourceFile, node *shimast.Node) bool {
|
|
79
|
+
raw := nodeText(file, node)
|
|
80
|
+
if len(raw) < 2 || raw[0] != '/' {
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
closing := strings.LastIndexByte(raw, '/')
|
|
84
|
+
if closing <= 0 {
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
flags := raw[closing+1:]
|
|
88
|
+
return strings.Contains(flags, "g")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// preferRegexpExecIsString reports whether t is provably string-like.
|
|
92
|
+
// Mirrors `preferStringStartsEndsWithIsString`: `any` / `unknown` /
|
|
93
|
+
// `never` are rejected so generic helpers don't fire. Union and
|
|
94
|
+
// intersection types must have every constituent string-like.
|
|
95
|
+
func preferRegexpExecIsString(t *shimchecker.Type) bool {
|
|
96
|
+
if t == nil {
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
flags := t.Flags()
|
|
100
|
+
if flags&(shimchecker.TypeFlagsAny|shimchecker.TypeFlagsUnknown|shimchecker.TypeFlagsNever) != 0 {
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
103
|
+
if flags&shimchecker.TypeFlagsStringLike != 0 {
|
|
104
|
+
return true
|
|
105
|
+
}
|
|
106
|
+
if flags&(shimchecker.TypeFlagsUnion|shimchecker.TypeFlagsIntersection) != 0 {
|
|
107
|
+
for _, part := range t.Types() {
|
|
108
|
+
if part == nil {
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
if !preferRegexpExecIsString(part) {
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return true
|
|
116
|
+
}
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
func init() {
|
|
121
|
+
Register(preferRegexpExec{})
|
|
122
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// typescript/prefer-return-this-type: when an instance method always
|
|
2
|
+
// `return this`, declare its return type as `this` instead of the
|
|
3
|
+
// enclosing class name. The `this` polymorphic-receiver type keeps the
|
|
4
|
+
// narrower subclass type at the call site so chained calls stay
|
|
5
|
+
// polymorphic: `subclass.fluent().subclassOnly()` only type-checks if
|
|
6
|
+
// `fluent()` is declared `: this` rather than `: Base`.
|
|
7
|
+
// typescript-eslint:
|
|
8
|
+
// https://typescript-eslint.io/rules/prefer-return-this-type/
|
|
9
|
+
//
|
|
10
|
+
// Type-aware. Without a Checker the rule cannot read the method's
|
|
11
|
+
// declared return type or distinguish the class symbol, so
|
|
12
|
+
// Context.Checker == nil short-circuits each Check to a no-op the way
|
|
13
|
+
// other type-aware rules do.
|
|
14
|
+
//
|
|
15
|
+
// Skipped:
|
|
16
|
+
// - methods whose return type is already `this`;
|
|
17
|
+
// - methods without an explicit declared return type (the upstream
|
|
18
|
+
// rule only proposes a narrower annotation when one is already
|
|
19
|
+
// present — adding a brand-new annotation is the job of
|
|
20
|
+
// `explicit-function-return-type`);
|
|
21
|
+
// - constructors, accessors, generators, and `async` methods (each
|
|
22
|
+
// has return-shape semantics the `this` rewrite does not preserve);
|
|
23
|
+
// - methods with no body (overload signatures, abstract members);
|
|
24
|
+
// - methods that have at least one `return X;` where `X` is not the
|
|
25
|
+
// `this` keyword.
|
|
26
|
+
package linthost
|
|
27
|
+
|
|
28
|
+
import (
|
|
29
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
type preferReturnThisType struct{}
|
|
33
|
+
|
|
34
|
+
func (preferReturnThisType) Name() string { return "typescript/prefer-return-this-type" }
|
|
35
|
+
func (preferReturnThisType) NeedsTypeChecker() bool {
|
|
36
|
+
return true
|
|
37
|
+
}
|
|
38
|
+
func (preferReturnThisType) Visits() []shimast.Kind {
|
|
39
|
+
return []shimast.Kind{shimast.KindMethodDeclaration}
|
|
40
|
+
}
|
|
41
|
+
func (preferReturnThisType) Check(ctx *Context, node *shimast.Node) {
|
|
42
|
+
if ctx.Checker == nil {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
decl := node.AsMethodDeclaration()
|
|
46
|
+
if decl == nil || decl.Body == nil {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
// Skip async / generator methods — their return shape is wrapped
|
|
50
|
+
// (`Promise<...>` / `Generator<...>`) and not directly `this`.
|
|
51
|
+
if hasAsyncModifier(node) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
if decl.AsteriskToken != nil {
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
// Skip static methods — the upstream rule targets instance-method
|
|
58
|
+
// chains where the receiver type is the subclass; static dispatch
|
|
59
|
+
// uses the class itself and `this` rewriting changes nothing.
|
|
60
|
+
if hasModifier(node, shimast.KindStaticKeyword) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
// The method must have an explicit return-type annotation that is
|
|
64
|
+
// NOT already `this`. The rewrite-target is the existing annotation;
|
|
65
|
+
// without one, adding `this` would force a new annotation that the
|
|
66
|
+
// upstream rule does not propose.
|
|
67
|
+
if decl.Type == nil {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
if decl.Type.Kind == shimast.KindThisType {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
// The enclosing class must exist — the rule applies to instance
|
|
74
|
+
// methods of class declarations / expressions only.
|
|
75
|
+
parent := node.Parent
|
|
76
|
+
if parent == nil {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
if parent.Kind != shimast.KindClassDeclaration &&
|
|
80
|
+
parent.Kind != shimast.KindClassExpression {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
// Walk the method body. Every value-returning `return` must
|
|
84
|
+
// return exactly `this`; we also require at least one return
|
|
85
|
+
// statement so we don't fire on methods that fall off the end.
|
|
86
|
+
hasValueReturn, allAreThis := preferReturnThisTypeAnalyzeBody(decl.Body)
|
|
87
|
+
if !hasValueReturn || !allAreThis {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
ctx.Report(decl.Type, preferReturnThisTypeMessage)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const preferReturnThisTypeMessage = "Method always returns `this` — declare the return type as `this` so subclass call sites keep the narrower receiver type."
|
|
94
|
+
|
|
95
|
+
// preferReturnThisTypeAnalyzeBody walks the method body (without
|
|
96
|
+
// descending into nested function-like scopes) and reports:
|
|
97
|
+
// - hasValueReturn: at least one `return <expression>;` exists.
|
|
98
|
+
// - allAreThis: every value-returning `return` returns the bare
|
|
99
|
+
// `this` keyword (after stripping parens).
|
|
100
|
+
//
|
|
101
|
+
// A bare `return;` is ignored — it returns `undefined`, which the rule
|
|
102
|
+
// cannot rewrite to `this` regardless. The conservative interpretation
|
|
103
|
+
// matches the upstream rule's behavior.
|
|
104
|
+
func preferReturnThisTypeAnalyzeBody(body *shimast.Node) (hasValueReturn, allAreThis bool) {
|
|
105
|
+
allAreThis = true
|
|
106
|
+
var walk func(*shimast.Node)
|
|
107
|
+
walk = func(n *shimast.Node) {
|
|
108
|
+
if n == nil {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
if n != body && isFunctionLikeKind(n) {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
if n.Kind == shimast.KindReturnStatement {
|
|
115
|
+
ret := n.AsReturnStatement()
|
|
116
|
+
if ret != nil && ret.Expression != nil {
|
|
117
|
+
hasValueReturn = true
|
|
118
|
+
inner := stripParens(ret.Expression)
|
|
119
|
+
if inner == nil || inner.Kind != shimast.KindThisKeyword {
|
|
120
|
+
allAreThis = false
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
n.ForEachChild(func(child *shimast.Node) bool {
|
|
125
|
+
walk(child)
|
|
126
|
+
return false
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
walk(body)
|
|
130
|
+
return hasValueReturn, allAreThis
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
func init() {
|
|
134
|
+
Register(preferReturnThisType{})
|
|
135
|
+
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
// typescript/prefer-string-starts-ends-with: prefer
|
|
2
|
+
// `str.startsWith(prefix)` / `str.endsWith(suffix)` over the older
|
|
3
|
+
// `indexOf` / `lastIndexOf` / anchored-regex shapes:
|
|
4
|
+
//
|
|
5
|
+
// - `str.indexOf(p) === 0` → startsWith
|
|
6
|
+
// - `str.indexOf(p, str.length - p.length) !== -1` → endsWith
|
|
7
|
+
// - `str.lastIndexOf(p) === str.length - p.length` → endsWith
|
|
8
|
+
// - `/^prefix/.test(str)` (a static-prefix regex) → startsWith
|
|
9
|
+
// - `/suffix$/.test(str)` (a static-suffix regex) → endsWith
|
|
10
|
+
//
|
|
11
|
+
// The dedicated methods state the intent directly and avoid the
|
|
12
|
+
// off-by-one arithmetic and regex-anchor pitfalls of the older shapes.
|
|
13
|
+
// typescript-eslint:
|
|
14
|
+
// https://typescript-eslint.io/rules/prefer-string-starts-ends-with/
|
|
15
|
+
//
|
|
16
|
+
// Type-aware. Without a Checker the rule cannot prove the receiver of
|
|
17
|
+
// `indexOf` / `lastIndexOf` (or the argument of `.test(str)`) is a
|
|
18
|
+
// `string`, so Context.Checker == nil short-circuits each Check to a
|
|
19
|
+
// no-op the way `no-base-to-string` and `prefer-includes` do. `any` /
|
|
20
|
+
// `unknown` / `never` are intentionally NOT treated as matching — they
|
|
21
|
+
// leak from generic helpers and would explode the false-positive
|
|
22
|
+
// volume on user-defined `indexOf` / `test` methods.
|
|
23
|
+
package linthost
|
|
24
|
+
|
|
25
|
+
import (
|
|
26
|
+
"strings"
|
|
27
|
+
|
|
28
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
29
|
+
shimchecker "github.com/microsoft/typescript-go/shim/checker"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
type preferStringStartsEndsWith struct{}
|
|
33
|
+
|
|
34
|
+
func (preferStringStartsEndsWith) Name() string {
|
|
35
|
+
return "typescript/prefer-string-starts-ends-with"
|
|
36
|
+
}
|
|
37
|
+
func (preferStringStartsEndsWith) NeedsTypeChecker() bool {
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
func (preferStringStartsEndsWith) Visits() []shimast.Kind {
|
|
41
|
+
return []shimast.Kind{
|
|
42
|
+
shimast.KindBinaryExpression,
|
|
43
|
+
shimast.KindCallExpression,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
func (preferStringStartsEndsWith) Check(ctx *Context, node *shimast.Node) {
|
|
47
|
+
if ctx.Checker == nil {
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
switch node.Kind {
|
|
51
|
+
case shimast.KindBinaryExpression:
|
|
52
|
+
bin := node.AsBinaryExpression()
|
|
53
|
+
if bin == nil || bin.OperatorToken == nil {
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
if !preferStringStartsEndsWithIsEqualityOp(bin.OperatorToken.Kind) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
if preferStringStartsEndsWithBinary(ctx, bin.Left, bin.Right) {
|
|
60
|
+
ctx.Report(node, preferStringStartsEndsWithMessage)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
if preferStringStartsEndsWithBinary(ctx, bin.Right, bin.Left) {
|
|
64
|
+
ctx.Report(node, preferStringStartsEndsWithMessage)
|
|
65
|
+
}
|
|
66
|
+
case shimast.KindCallExpression:
|
|
67
|
+
call := node.AsCallExpression()
|
|
68
|
+
if call == nil {
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
if preferStringStartsEndsWithRegexTest(ctx, call) {
|
|
72
|
+
ctx.Report(node, preferStringStartsEndsWithMessage)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const preferStringStartsEndsWithMessage = "Prefer `String#startsWith` / `String#endsWith` over the equivalent `indexOf` / `lastIndexOf` / anchored-regex idioms — clearer and avoids the off-by-one arithmetic."
|
|
78
|
+
|
|
79
|
+
// preferStringStartsEndsWithIsEqualityOp reports whether `kind` is one
|
|
80
|
+
// of the equality tokens the rule cares about. Only `==`, `===`,
|
|
81
|
+
// `!=`, `!==` matter: every recognized shape compares two integers
|
|
82
|
+
// for equality (or inequality with a sentinel) rather than ordering.
|
|
83
|
+
func preferStringStartsEndsWithIsEqualityOp(kind shimast.Kind) bool {
|
|
84
|
+
switch kind {
|
|
85
|
+
case shimast.KindEqualsEqualsToken,
|
|
86
|
+
shimast.KindEqualsEqualsEqualsToken,
|
|
87
|
+
shimast.KindExclamationEqualsToken,
|
|
88
|
+
shimast.KindExclamationEqualsEqualsToken:
|
|
89
|
+
return true
|
|
90
|
+
}
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// preferStringStartsEndsWithBinary inspects `call op other` for the
|
|
95
|
+
// `indexOf` / `lastIndexOf` shapes. Returns true when the receiver of
|
|
96
|
+
// the call is provably string-typed AND the right-hand side matches
|
|
97
|
+
// the sentinel of one of the recognized rewrites:
|
|
98
|
+
//
|
|
99
|
+
// str.indexOf(p) === 0
|
|
100
|
+
// str.indexOf(p, str.length - p.length) !== -1
|
|
101
|
+
// str.lastIndexOf(p) === str.length - p.length
|
|
102
|
+
func preferStringStartsEndsWithBinary(
|
|
103
|
+
ctx *Context,
|
|
104
|
+
callNode, otherNode *shimast.Node,
|
|
105
|
+
) bool {
|
|
106
|
+
callNode = stripParens(callNode)
|
|
107
|
+
otherNode = stripParens(otherNode)
|
|
108
|
+
if callNode == nil || otherNode == nil {
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
if callNode.Kind != shimast.KindCallExpression {
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
call := callNode.AsCallExpression()
|
|
115
|
+
if call == nil || call.Expression == nil {
|
|
116
|
+
return false
|
|
117
|
+
}
|
|
118
|
+
receiver, method, ok := promisePropertyAccessParts(call.Expression)
|
|
119
|
+
if !ok {
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
if method != "indexOf" && method != "lastIndexOf" {
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
if receiver == nil {
|
|
126
|
+
return false
|
|
127
|
+
}
|
|
128
|
+
t := ctx.Checker.GetTypeAtLocation(receiver)
|
|
129
|
+
if !preferStringStartsEndsWithIsString(t) {
|
|
130
|
+
return false
|
|
131
|
+
}
|
|
132
|
+
args := call.Arguments
|
|
133
|
+
if args == nil || len(args.Nodes) == 0 {
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
arg := stripParens(args.Nodes[0])
|
|
137
|
+
if arg == nil {
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
receiverText := nodeText(ctx.File, receiver)
|
|
141
|
+
argText := nodeText(ctx.File, arg)
|
|
142
|
+
otherText := nodeText(ctx.File, otherNode)
|
|
143
|
+
if receiverText == "" || argText == "" {
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
switch method {
|
|
147
|
+
case "indexOf":
|
|
148
|
+
// startsWith: `str.indexOf(p) === 0`
|
|
149
|
+
if len(args.Nodes) == 1 {
|
|
150
|
+
return otherText == "0"
|
|
151
|
+
}
|
|
152
|
+
// endsWith via positional indexOf: the second argument must
|
|
153
|
+
// equal `str.length - arg.length`, and the comparison must be
|
|
154
|
+
// against -1 (the sentinel for "not present at that
|
|
155
|
+
// position").
|
|
156
|
+
if len(args.Nodes) != 2 {
|
|
157
|
+
return false
|
|
158
|
+
}
|
|
159
|
+
second := stripParens(args.Nodes[1])
|
|
160
|
+
if !preferStringStartsEndsWithIsLengthMinusLength(ctx, second, receiverText, argText) {
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
163
|
+
return preferStringStartsEndsWithIsMinusOne(otherText)
|
|
164
|
+
case "lastIndexOf":
|
|
165
|
+
// endsWith: `str.lastIndexOf(p) === str.length - p.length`
|
|
166
|
+
if len(args.Nodes) != 1 {
|
|
167
|
+
return false
|
|
168
|
+
}
|
|
169
|
+
return preferStringStartsEndsWithIsLengthMinusLengthText(otherText, receiverText, argText)
|
|
170
|
+
}
|
|
171
|
+
return false
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// preferStringStartsEndsWithRegexTest reports whether `call` is a
|
|
175
|
+
// `/^.../.test(str)` or `/...$/.test(str)` invocation on a regex
|
|
176
|
+
// literal whose body is a static prefix or suffix — no metacharacters
|
|
177
|
+
// other than the anchor and no flags that change the matching
|
|
178
|
+
// semantics. The argument to `.test` must be a `string`-typed value;
|
|
179
|
+
// without that the call is not equivalent to a startsWith/endsWith
|
|
180
|
+
// rewrite (e.g. coercing a non-string regex argument).
|
|
181
|
+
func preferStringStartsEndsWithRegexTest(
|
|
182
|
+
ctx *Context,
|
|
183
|
+
call *shimast.CallExpression,
|
|
184
|
+
) bool {
|
|
185
|
+
if call == nil || call.Expression == nil || call.Arguments == nil || len(call.Arguments.Nodes) != 1 {
|
|
186
|
+
return false
|
|
187
|
+
}
|
|
188
|
+
receiver, method, ok := promisePropertyAccessParts(call.Expression)
|
|
189
|
+
if !ok || method != "test" {
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
receiver = stripParens(receiver)
|
|
193
|
+
if receiver == nil || receiver.Kind != shimast.KindRegularExpressionLiteral {
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
raw := nodeText(ctx.File, receiver)
|
|
197
|
+
if !preferStringStartsEndsWithIsAnchoredStaticRegex(raw) {
|
|
198
|
+
return false
|
|
199
|
+
}
|
|
200
|
+
arg := stripParens(call.Arguments.Nodes[0])
|
|
201
|
+
if arg == nil {
|
|
202
|
+
return false
|
|
203
|
+
}
|
|
204
|
+
return preferStringStartsEndsWithIsString(ctx.Checker.GetTypeAtLocation(arg))
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// preferStringStartsEndsWithIsAnchoredStaticRegex reports whether
|
|
208
|
+
// `raw` (the source text of a RegularExpressionLiteral, including the
|
|
209
|
+
// surrounding `/`) encodes either `^prefix` or `suffix$` with no
|
|
210
|
+
// metacharacters other than the anchor itself, and carries no flags
|
|
211
|
+
// that change matching semantics (`i`, `m`, `s`, `u`, `v`). A flag
|
|
212
|
+
// like `i` would lower-case both sides, which `startsWith` does not
|
|
213
|
+
// do — so the rewrite would change meaning.
|
|
214
|
+
func preferStringStartsEndsWithIsAnchoredStaticRegex(raw string) bool {
|
|
215
|
+
if len(raw) < 3 || raw[0] != '/' {
|
|
216
|
+
return false
|
|
217
|
+
}
|
|
218
|
+
closing := strings.LastIndexByte(raw, '/')
|
|
219
|
+
if closing <= 0 {
|
|
220
|
+
return false
|
|
221
|
+
}
|
|
222
|
+
pattern := raw[1:closing]
|
|
223
|
+
flags := raw[closing+1:]
|
|
224
|
+
if pattern == "" {
|
|
225
|
+
return false
|
|
226
|
+
}
|
|
227
|
+
for i := 0; i < len(flags); i++ {
|
|
228
|
+
switch flags[i] {
|
|
229
|
+
case 'g', 'y', 'd':
|
|
230
|
+
// stateful/position flags — fine for `.test` since we
|
|
231
|
+
// only call once
|
|
232
|
+
default:
|
|
233
|
+
return false
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if pattern[0] == '^' {
|
|
237
|
+
return preferStringStartsEndsWithIsStaticString(pattern[1:])
|
|
238
|
+
}
|
|
239
|
+
if pattern[len(pattern)-1] == '$' {
|
|
240
|
+
return preferStringStartsEndsWithIsStaticString(pattern[:len(pattern)-1])
|
|
241
|
+
}
|
|
242
|
+
return false
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// preferStringStartsEndsWithIsStaticString reports whether `pattern`
|
|
246
|
+
// is a regex body made up only of literal characters (and accepted
|
|
247
|
+
// escape sequences `\.`, `\\`, etc. that represent a single literal
|
|
248
|
+
// character). Any metacharacter — `^`, `$`, `*`, `+`, `?`, `(`, `)`,
|
|
249
|
+
// `[`, `]`, `{`, `}`, `|`, `.` — disqualifies the body because the
|
|
250
|
+
// startsWith/endsWith rewrite would change matching semantics.
|
|
251
|
+
func preferStringStartsEndsWithIsStaticString(pattern string) bool {
|
|
252
|
+
if pattern == "" {
|
|
253
|
+
return false
|
|
254
|
+
}
|
|
255
|
+
for i := 0; i < len(pattern); i++ {
|
|
256
|
+
ch := pattern[i]
|
|
257
|
+
if ch == '\\' {
|
|
258
|
+
// Accept only escapes that name a single literal
|
|
259
|
+
// character (`\.`, `\$`, `\^`, `\/`, `\\`, `\(`, `\)`,
|
|
260
|
+
// `\[`, `\]`, `\{`, `\}`, `\|`, `\+`, `\*`, `\?`).
|
|
261
|
+
// Reject character-class shortcuts (`\d`, `\w`, `\s`)
|
|
262
|
+
// and back-references — those would not survive a
|
|
263
|
+
// rewrite to startsWith/endsWith.
|
|
264
|
+
if i+1 >= len(pattern) {
|
|
265
|
+
return false
|
|
266
|
+
}
|
|
267
|
+
next := pattern[i+1]
|
|
268
|
+
switch next {
|
|
269
|
+
case '.', '$', '^', '/', '\\', '(', ')', '[', ']', '{', '}', '|', '+', '*', '?', '-':
|
|
270
|
+
i++
|
|
271
|
+
continue
|
|
272
|
+
}
|
|
273
|
+
return false
|
|
274
|
+
}
|
|
275
|
+
switch ch {
|
|
276
|
+
case '^', '$', '*', '+', '?', '(', ')', '[', ']', '{', '}', '|', '.':
|
|
277
|
+
return false
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return true
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// preferStringStartsEndsWithIsLengthMinusLength reports whether
|
|
284
|
+
// `node` textually evaluates to `<receiver>.length - <arg>.length`.
|
|
285
|
+
// The comparison is text-based (after stripping parens) because the
|
|
286
|
+
// rewrite only fires when the call site spelled the offset that way
|
|
287
|
+
// — matching the upstream rule's textual shape detector.
|
|
288
|
+
func preferStringStartsEndsWithIsLengthMinusLength(
|
|
289
|
+
ctx *Context,
|
|
290
|
+
node *shimast.Node,
|
|
291
|
+
receiverText, argText string,
|
|
292
|
+
) bool {
|
|
293
|
+
if node == nil {
|
|
294
|
+
return false
|
|
295
|
+
}
|
|
296
|
+
return preferStringStartsEndsWithIsLengthMinusLengthText(nodeText(ctx.File, node), receiverText, argText)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// preferStringStartsEndsWithIsLengthMinusLengthText is the text-level
|
|
300
|
+
// half of `preferStringStartsEndsWithIsLengthMinusLength` — split out
|
|
301
|
+
// so the `lastIndexOf === str.length - p.length` shape can use the
|
|
302
|
+
// same matcher against the binary RHS text.
|
|
303
|
+
func preferStringStartsEndsWithIsLengthMinusLengthText(text, receiverText, argText string) bool {
|
|
304
|
+
wanted := receiverText + ".length-" + argText + ".length"
|
|
305
|
+
return preferStringStartsEndsWithStripSpaces(text) == wanted
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// preferStringStartsEndsWithStripSpaces removes whitespace from `s`.
|
|
309
|
+
// Used for the textual equality match — `a.length - b.length` and
|
|
310
|
+
// `a.length-b.length` should both match the canonical form.
|
|
311
|
+
func preferStringStartsEndsWithStripSpaces(s string) string {
|
|
312
|
+
var b strings.Builder
|
|
313
|
+
for i := 0; i < len(s); i++ {
|
|
314
|
+
ch := s[i]
|
|
315
|
+
if ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' {
|
|
316
|
+
continue
|
|
317
|
+
}
|
|
318
|
+
b.WriteByte(ch)
|
|
319
|
+
}
|
|
320
|
+
return b.String()
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// preferStringStartsEndsWithIsMinusOne reports whether `text` is the
|
|
324
|
+
// numeric literal `-1` (with or without internal whitespace).
|
|
325
|
+
func preferStringStartsEndsWithIsMinusOne(text string) bool {
|
|
326
|
+
return preferStringStartsEndsWithStripSpaces(text) == "-1"
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// preferStringStartsEndsWithIsString reports whether t is provably
|
|
330
|
+
// string-like. Mirrors `isStringLikeType` in no-base-to-string but
|
|
331
|
+
// rejects `any` / `unknown` / `never` so generic helpers don't fire.
|
|
332
|
+
// Union and intersection types must have every constituent string-like.
|
|
333
|
+
func preferStringStartsEndsWithIsString(t *shimchecker.Type) bool {
|
|
334
|
+
if t == nil {
|
|
335
|
+
return false
|
|
336
|
+
}
|
|
337
|
+
flags := t.Flags()
|
|
338
|
+
if flags&(shimchecker.TypeFlagsAny|shimchecker.TypeFlagsUnknown|shimchecker.TypeFlagsNever) != 0 {
|
|
339
|
+
return false
|
|
340
|
+
}
|
|
341
|
+
if flags&shimchecker.TypeFlagsStringLike != 0 {
|
|
342
|
+
return true
|
|
343
|
+
}
|
|
344
|
+
if flags&(shimchecker.TypeFlagsUnion|shimchecker.TypeFlagsIntersection) != 0 {
|
|
345
|
+
for _, part := range t.Types() {
|
|
346
|
+
if part == nil {
|
|
347
|
+
continue
|
|
348
|
+
}
|
|
349
|
+
if !preferStringStartsEndsWithIsString(part) {
|
|
350
|
+
return false
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return true
|
|
354
|
+
}
|
|
355
|
+
return false
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
func init() {
|
|
359
|
+
Register(preferStringStartsEndsWith{})
|
|
360
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// promiseFunctionAsync requires a function whose return type is
|
|
2
|
+
// `Promise<T>` (or a Promise-like thenable) to be declared with the
|
|
3
|
+
// `async` keyword. typescript-eslint:
|
|
4
|
+
// https://typescript-eslint.io/rules/promise-function-async/
|
|
5
|
+
//
|
|
6
|
+
// Type-aware. An `async` function wraps a synchronous `throw` into a
|
|
7
|
+
// rejected Promise so the caller's `await` / `.catch(...)` observes
|
|
8
|
+
// it; the non-async equivalent throws synchronously and bypasses every
|
|
9
|
+
// Promise-aware handler downstream. Marking the function `async` keeps
|
|
10
|
+
// the rejection channel consistent with the declared return type.
|
|
11
|
+
//
|
|
12
|
+
// Skipped:
|
|
13
|
+
// - functions already declared `async` (the contract holds);
|
|
14
|
+
// - abstract methods and overload signatures (no body to wrap — the
|
|
15
|
+
// `async` keyword can only be applied to the implementation);
|
|
16
|
+
// - functions whose declared / inferred return type is not a
|
|
17
|
+
// Promise (`any` / `unknown` / `never` pass through, matching the
|
|
18
|
+
// conservative `isPromiseTypedExpression` baseline used by the
|
|
19
|
+
// surrounding async family).
|
|
20
|
+
package linthost
|
|
21
|
+
|
|
22
|
+
import (
|
|
23
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
type promiseFunctionAsync struct{}
|
|
27
|
+
|
|
28
|
+
func (promiseFunctionAsync) Name() string { return "typescript/promise-function-async" }
|
|
29
|
+
func (promiseFunctionAsync) NeedsTypeChecker() bool {
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
func (promiseFunctionAsync) Visits() []shimast.Kind {
|
|
33
|
+
return []shimast.Kind{
|
|
34
|
+
shimast.KindFunctionDeclaration,
|
|
35
|
+
shimast.KindFunctionExpression,
|
|
36
|
+
shimast.KindArrowFunction,
|
|
37
|
+
shimast.KindMethodDeclaration,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
func (promiseFunctionAsync) Check(ctx *Context, node *shimast.Node) {
|
|
41
|
+
if ctx.Checker == nil {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
if hasAsyncModifier(node) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
// Skip overload signatures and abstract methods — only the
|
|
48
|
+
// implementation signature carries an executable body that the
|
|
49
|
+
// `async` keyword could wrap.
|
|
50
|
+
if node.Body() == nil {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
if hasModifier(node, shimast.KindAbstractKeyword) {
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
sig := ctx.Checker.GetSignatureFromDeclaration(node)
|
|
57
|
+
if sig == nil {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
returnType := ctx.Checker.GetReturnTypeOfSignature(sig)
|
|
61
|
+
if returnType == nil {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
if !isPromiseTypedExpression(ctx.Checker, returnType) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
startPos := keywordStart(ctx.File, node, "function")
|
|
68
|
+
if startPos >= 0 {
|
|
69
|
+
ctx.ReportRange(startPos, startPos+len("function"), "Function returning a Promise should be declared `async` so synchronous throws surface as a rejected Promise.")
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
ctx.Report(node, "Function returning a Promise should be declared `async` so synchronous throws surface as a rejected Promise.")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func init() {
|
|
76
|
+
Register(promiseFunctionAsync{})
|
|
77
|
+
}
|