@ttsc/lint 0.14.0-dev.20260529.2 → 0.14.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 +23 -25
- package/lib/defaultFormat.d.ts +10 -6
- package/lib/defaultFormat.js +10 -6
- package/lib/defaultFormat.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +5 -5
- package/lib/structures/TtscLintRuleSetting.d.ts +19 -19
- package/lib/structures/{ITtscLintFormat.d.ts → format/ITtscLintFormat.d.ts} +45 -70
- package/lib/structures/format/ITtscLintFormat.js.map +1 -0
- package/lib/structures/format/ITtscLintFormatJsDoc.d.ts +15 -0
- package/lib/structures/format/ITtscLintFormatJsDoc.js +3 -0
- package/lib/structures/format/ITtscLintFormatJsDoc.js.map +1 -0
- package/lib/structures/format/ITtscLintFormatSortImports.d.ts +54 -0
- package/lib/structures/format/ITtscLintFormatSortImports.js +3 -0
- package/lib/structures/format/ITtscLintFormatSortImports.js.map +1 -0
- package/lib/structures/format/index.d.ts +3 -0
- package/lib/structures/format/index.js +20 -0
- package/lib/structures/format/index.js.map +1 -0
- package/lib/structures/index.d.ts +1 -1
- package/lib/structures/index.js +1 -1
- package/lib/structures/index.js.map +1 -1
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +18 -27
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js +4 -5
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js.map +1 -1
- package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +29 -33
- package/lib/structures/rules/ITtscLintContributorRules.d.ts +6 -7
- package/lib/structures/rules/ITtscLintCoreRules.d.ts +380 -408
- package/lib/structures/rules/ITtscLintCypressRuleOptions.d.ts +5 -5
- package/lib/structures/rules/ITtscLintCypressRuleOptions.js +2 -2
- package/lib/structures/rules/ITtscLintCypressRules.d.ts +34 -38
- package/lib/structures/rules/ITtscLintFunctionalRuleOptions.d.ts +21 -24
- package/lib/structures/rules/ITtscLintFunctionalRuleOptions.js +2 -2
- package/lib/structures/rules/ITtscLintFunctionalRules.d.ts +51 -56
- package/lib/structures/rules/ITtscLintJestRules.d.ts +63 -74
- package/lib/structures/rules/ITtscLintJsdocRules.d.ts +47 -54
- package/lib/structures/rules/ITtscLintJsxA11yRules.d.ts +94 -102
- package/lib/structures/rules/ITtscLintNextjsRules.d.ts +50 -55
- package/lib/structures/rules/ITtscLintPlaywrightRules.d.ts +104 -122
- package/lib/structures/rules/ITtscLintPromiseRules.d.ts +62 -71
- package/lib/structures/rules/ITtscLintReactPerfRuleOptions.d.ts +4 -4
- package/lib/structures/rules/ITtscLintReactPerfRules.d.ts +17 -19
- package/lib/structures/rules/ITtscLintReactRuleOptions.d.ts +8 -10
- package/lib/structures/rules/ITtscLintReactRuleOptions.js +1 -2
- package/lib/structures/rules/ITtscLintReactRuleOptions.js.map +1 -1
- package/lib/structures/rules/ITtscLintReactRules.d.ts +91 -104
- package/lib/structures/rules/ITtscLintRegexpRules.d.ts +46 -54
- package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +7 -8
- package/lib/structures/rules/ITtscLintSecurityRules.d.ts +45 -52
- package/lib/structures/rules/ITtscLintSolidRules.d.ts +53 -56
- package/lib/structures/rules/ITtscLintStorybookRuleOptions.d.ts +3 -4
- package/lib/structures/rules/ITtscLintStorybookRules.d.ts +60 -69
- package/lib/structures/rules/ITtscLintTanstackQueryRules.d.ts +31 -37
- package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.d.ts +2 -3
- package/lib/structures/rules/ITtscLintTestingLibraryRules.d.ts +102 -119
- package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +460 -518
- package/lib/structures/rules/ITtscLintVitestRules.d.ts +43 -51
- package/linthost/config.go +89 -72
- package/linthost/config_format.go +265 -99
- package/linthost/directives.go +5 -1
- package/linthost/display_width.go +72 -0
- package/linthost/fix.go +26 -1
- package/linthost/format.go +222 -25
- package/linthost/format_editor_settings.go +262 -0
- package/linthost/lsp.go +8 -4
- package/linthost/print_dispatch.go +3 -0
- package/linthost/print_doc.go +13 -0
- package/linthost/print_engine.go +68 -11
- package/linthost/print_nodes_array.go +167 -5
- package/linthost/print_nodes_call.go +786 -77
- package/linthost/print_nodes_function.go +43 -0
- package/linthost/print_nodes_imports.go +64 -14
- package/linthost/print_nodes_list.go +214 -8
- package/linthost/print_nodes_object.go +14 -6
- package/linthost/print_nodes_ternary.go +147 -0
- package/linthost/rules_boundaries.go +400 -400
- package/linthost/rules_camelcase.go +81 -81
- package/linthost/rules_complexity.go +52 -51
- package/linthost/rules_consistent_return.go +81 -81
- package/linthost/rules_core_extra.go +472 -472
- package/linthost/rules_curly.go +43 -43
- package/linthost/rules_default_case.go +15 -15
- package/linthost/rules_default_case_last.go +22 -22
- package/linthost/rules_format_arrow_parens.go +213 -0
- package/linthost/rules_format_bracket_spacing.go +117 -0
- package/linthost/rules_format_clause_join.go +179 -0
- package/linthost/rules_format_declaration_header.go +664 -0
- package/linthost/rules_format_indent.go +616 -0
- package/linthost/rules_format_jsdoc.go +4 -4
- package/linthost/rules_format_orphan_semi.go +106 -0
- package/linthost/rules_format_parameter_properties.go +175 -0
- package/linthost/rules_format_print_width.go +180 -69
- package/linthost/rules_format_quote_props.go +169 -0
- package/linthost/rules_format_quotes.go +75 -29
- package/linthost/rules_format_semi.go +185 -10
- package/linthost/rules_format_sort_imports.go +517 -121
- package/linthost/rules_format_statement_split.go +368 -0
- package/linthost/rules_format_ternary_nullish_parens.go +77 -0
- package/linthost/rules_format_trailing_comma.go +21 -2
- package/linthost/rules_format_whitespace.go +336 -0
- package/linthost/rules_functional.go +688 -688
- package/linthost/rules_gap.go +146 -11
- package/linthost/rules_grouped_accessor_pairs.go +60 -60
- package/linthost/rules_guard_for_in.go +122 -122
- package/linthost/rules_id_length.go +44 -44
- package/linthost/rules_imports.go +11 -3
- package/linthost/rules_init_declarations.go +25 -25
- package/linthost/rules_jsx_a11y.go +844 -844
- package/linthost/rules_max_classes_per_file.go +24 -24
- package/linthost/rules_max_depth.go +43 -43
- package/linthost/rules_max_lines.go +53 -53
- package/linthost/rules_max_lines_per_function.go +28 -28
- package/linthost/rules_max_nested_callbacks.go +26 -26
- package/linthost/rules_max_params.go +20 -20
- package/linthost/rules_max_statements.go +29 -29
- package/linthost/rules_nextjs.go +5 -5
- package/linthost/rules_no_else_return.go +38 -38
- package/linthost/rules_no_empty_named_blocks.go +45 -45
- package/linthost/rules_no_import_assign.go +96 -96
- package/linthost/rules_no_invalid_this.go +19 -19
- package/linthost/rules_no_loop_func.go +43 -43
- package/linthost/rules_no_magic_numbers.go +60 -60
- package/linthost/rules_no_mixed_operators.go +44 -44
- package/linthost/rules_no_param_reassign.go +64 -64
- package/linthost/rules_no_restricted_imports.go +45 -45
- package/linthost/rules_no_restricted_syntax.go +17 -17
- package/linthost/rules_no_shadow.go +112 -112
- package/linthost/rules_no_unreachable.go +36 -36
- package/linthost/rules_no_useless_assignment.go +79 -79
- package/linthost/rules_no_useless_constructor.go +167 -167
- package/linthost/rules_no_useless_return.go +28 -28
- package/linthost/rules_prefer_arrow_callback.go +74 -74
- package/linthost/rules_prefer_destructuring.go +61 -61
- package/linthost/rules_prefer_named_capture_group.go +87 -87
- package/linthost/rules_prefer_rest_params.go +61 -61
- package/linthost/rules_promise.go +800 -800
- package/linthost/rules_react.go +600 -600
- package/linthost/rules_react_extras.go +162 -162
- package/linthost/rules_react_hooks.go +2 -2
- package/linthost/rules_react_perf.go +231 -231
- package/linthost/rules_regexp.go +293 -293
- package/linthost/rules_security.go +613 -613
- package/linthost/rules_sort_imports.go +37 -37
- package/linthost/rules_sort_keys.go +88 -88
- package/linthost/rules_suggestions.go +13 -0
- package/linthost/rules_ts.go +2 -2
- package/linthost/rules_ts_async.go +247 -247
- package/linthost/rules_ts_class_literal_property_style.go +64 -64
- package/linthost/rules_ts_consistent_type_exports.go +209 -209
- package/linthost/rules_ts_explicit_function_return_type.go +35 -35
- package/linthost/rules_ts_extra.go +6 -2
- package/linthost/rules_ts_no_base_to_string.go +140 -140
- package/linthost/rules_ts_no_confusing_void.go +28 -28
- package/linthost/rules_ts_no_deprecated.go +110 -110
- package/linthost/rules_ts_no_for_in_array.go +45 -45
- package/linthost/rules_ts_no_magic_numbers.go +67 -67
- package/linthost/rules_ts_no_meaningless_void_operator.go +44 -44
- package/linthost/rules_ts_no_misused_spread.go +64 -64
- package/linthost/rules_ts_no_redundant_type_constituents.go +72 -72
- package/linthost/rules_ts_no_restricted_types.go +28 -28
- package/linthost/rules_ts_no_unnecessary_boolean_literal_compare.go +87 -87
- package/linthost/rules_ts_no_unnecessary_condition.go +184 -184
- package/linthost/rules_ts_no_unnecessary_qualifier.go +72 -72
- package/linthost/rules_ts_no_unnecessary_template_expression.go +120 -120
- package/linthost/rules_ts_no_unnecessary_type_arguments.go +171 -171
- package/linthost/rules_ts_no_unnecessary_type_assertion.go +90 -90
- package/linthost/rules_ts_no_unsafe_argument.go +50 -50
- package/linthost/rules_ts_no_unsafe_assignment.go +60 -60
- package/linthost/rules_ts_no_unsafe_call.go +40 -40
- package/linthost/rules_ts_no_unsafe_enum_comparison.go +147 -147
- package/linthost/rules_ts_no_unsafe_member_access.go +29 -29
- package/linthost/rules_ts_no_unsafe_return.go +64 -64
- package/linthost/rules_ts_no_unsafe_unary_minus.go +42 -42
- package/linthost/rules_ts_no_useless_constructor.go +20 -20
- package/linthost/rules_ts_non_nullable_type_assertion_style.go +60 -60
- package/linthost/rules_ts_parameter_properties.go +10 -10
- package/linthost/rules_ts_prefer_find.go +116 -116
- package/linthost/rules_ts_prefer_includes.go +173 -173
- package/linthost/rules_ts_prefer_nullish_coalescing.go +54 -54
- package/linthost/rules_ts_prefer_optional_chain.go +156 -156
- package/linthost/rules_ts_prefer_promise_reject_errors.go +104 -104
- package/linthost/rules_ts_prefer_readonly.go +31 -31
- package/linthost/rules_ts_prefer_reduce_type_parameter.go +74 -74
- package/linthost/rules_ts_prefer_regexp_exec.go +69 -69
- package/linthost/rules_ts_prefer_return_this_type.go +79 -79
- package/linthost/rules_ts_prefer_string_starts_ends_with.go +237 -237
- package/linthost/rules_ts_promise_function_async.go +41 -41
- package/linthost/rules_ts_related_getter_setter_pairs.go +61 -61
- package/linthost/rules_ts_require_array_sort_compare.go +58 -58
- package/linthost/rules_ts_restrict_plus_operands.go +82 -82
- package/linthost/rules_ts_restrict_template_expressions.go +50 -50
- package/linthost/rules_ts_simple.go +249 -249
- package/linthost/rules_ts_sort_type_constituents.go +88 -88
- package/linthost/rules_ts_strict_boolean_expressions.go +103 -103
- package/linthost/rules_ts_switch_exhaustiveness_check.go +90 -90
- package/linthost/rules_ts_unbound_method.go +65 -65
- package/linthost/rules_tsdoc.go +88 -88
- package/linthost/rules_unicorn_better_regex.go +1 -1
- package/linthost/rules_unicorn_catch_error_name.go +19 -19
- package/linthost/rules_unicorn_consistent_assert.go +20 -20
- package/linthost/rules_unicorn_consistent_date_clone.go +27 -27
- package/linthost/rules_unicorn_consistent_destructuring.go +3 -3
- package/linthost/rules_unicorn_consistent_empty_array_spread.go +34 -34
- package/linthost/rules_unicorn_consistent_existence_index_check.go +42 -42
- package/linthost/rules_unicorn_consistent_function_scoping.go +3 -3
- package/linthost/rules_unicorn_consistent_template_literal_escape.go +3 -3
- package/linthost/rules_unicorn_custom_error_definition.go +92 -92
- package/linthost/rules_unicorn_empty_brace_spaces.go +47 -47
- package/linthost/rules_unicorn_error_message.go +34 -34
- package/linthost/rules_unicorn_escape_case.go +14 -14
- package/linthost/rules_unicorn_expiring_todo_comments.go +36 -36
- package/linthost/rules_unicorn_explicit_length_check.go +51 -51
- package/linthost/rules_unicorn_filename_case.go +1 -1
- package/linthost/rules_unicorn_import_style.go +1 -1
- package/linthost/rules_unicorn_isolated_functions.go +2 -2
- package/linthost/rules_unicorn_new_for_builtins.go +49 -49
- package/linthost/rules_unicorn_no_abusive_eslint_disable.go +31 -31
- package/linthost/rules_unicorn_no_accessor_recursion.go +69 -69
- package/linthost/rules_unicorn_no_anonymous_default_export.go +40 -40
- package/linthost/rules_unicorn_no_array_callback_reference.go +33 -33
- package/linthost/rules_unicorn_no_array_for_each.go +14 -14
- package/linthost/rules_unicorn_no_array_method_this_argument.go +29 -29
- package/linthost/rules_unicorn_no_array_reduce.go +15 -15
- package/linthost/rules_unicorn_no_array_reverse.go +17 -17
- package/linthost/rules_unicorn_no_array_sort.go +17 -17
- package/linthost/rules_unicorn_no_await_expression_member.go +12 -12
- package/linthost/rules_unicorn_no_await_in_promise_methods.go +38 -38
- package/linthost/rules_unicorn_no_console_spaces.go +30 -30
- package/linthost/rules_unicorn_no_document_cookie.go +13 -13
- package/linthost/rules_unicorn_no_empty_file.go +16 -16
- package/linthost/rules_unicorn_no_for_loop.go +58 -58
- package/linthost/rules_unicorn_no_hex_escape.go +11 -11
- package/linthost/rules_unicorn_no_immediate_mutation.go +55 -55
- package/linthost/rules_unicorn_no_instanceof_builtins.go +34 -34
- package/linthost/rules_unicorn_no_invalid_fetch_options.go +52 -52
- package/linthost/rules_unicorn_no_invalid_remove_event_listener.go +25 -25
- package/linthost/rules_unicorn_no_keyword_prefix.go +56 -56
- package/linthost/rules_unicorn_no_lonely_if.go +21 -21
- package/linthost/rules_unicorn_no_magic_array_flat_depth.go +22 -22
- package/linthost/rules_unicorn_no_named_default.go +25 -25
- package/linthost/rules_unicorn_no_negated_condition.go +54 -54
- package/linthost/rules_unicorn_no_negation_in_equality_check.go +23 -23
- package/linthost/rules_unicorn_no_nested_ternary.go +15 -15
- package/linthost/rules_unicorn_no_new_array.go +5 -5
- package/linthost/rules_unicorn_no_new_buffer.go +5 -5
- package/linthost/rules_unicorn_no_null.go +2 -2
- package/linthost/rules_unicorn_no_object_as_default_parameter.go +19 -19
- package/linthost/rules_unicorn_no_process_exit.go +17 -17
- package/linthost/rules_unicorn_no_single_promise_in_promise_methods.go +29 -29
- package/linthost/rules_unicorn_no_static_only_class.go +27 -27
- package/linthost/rules_unicorn_no_thenable.go +47 -47
- package/linthost/rules_unicorn_no_this_assignment.go +10 -10
- package/linthost/rules_unicorn_no_typeof_undefined.go +31 -31
- package/linthost/rules_unicorn_no_unnecessary_array_flat_depth.go +23 -23
- package/linthost/rules_unicorn_no_unnecessary_array_splice_count.go +37 -37
- package/linthost/rules_unicorn_no_unnecessary_await.go +24 -24
- package/linthost/rules_unicorn_no_unnecessary_slice_end.go +22 -22
- package/linthost/rules_unicorn_no_unreadable_array_destructuring.go +67 -67
- package/linthost/rules_unicorn_no_unreadable_iife.go +22 -22
- package/linthost/rules_unicorn_no_useless_collection_argument.go +34 -34
- package/linthost/rules_unicorn_no_useless_error_capture_stack_trace.go +18 -18
- package/linthost/rules_unicorn_no_useless_fallback_in_spread.go +49 -49
- package/linthost/rules_unicorn_no_useless_iterator_to_array.go +41 -41
- package/linthost/rules_unicorn_no_useless_length_check.go +82 -82
- package/linthost/rules_unicorn_no_useless_promise_resolve_reject.go +42 -42
- package/linthost/rules_unicorn_no_useless_spread.go +38 -38
- package/linthost/rules_unicorn_no_useless_switch_case.go +29 -29
- package/linthost/rules_unicorn_no_useless_undefined.go +25 -25
- package/linthost/rules_unicorn_no_zero_fractions.go +17 -17
- package/linthost/rules_unicorn_number_literal_case.go +33 -33
- package/linthost/rules_unicorn_numeric_separators_style.go +36 -36
- package/linthost/rules_unicorn_prefer_add_event_listener.go +30 -30
- package/linthost/rules_unicorn_prefer_array_find.go +27 -27
- package/linthost/rules_unicorn_prefer_array_flat.go +25 -25
- package/linthost/rules_unicorn_prefer_array_flat_map.go +37 -37
- package/linthost/rules_unicorn_prefer_array_index_of.go +108 -108
- package/linthost/rules_unicorn_prefer_array_some.go +43 -43
- package/linthost/rules_unicorn_prefer_at.go +42 -42
- package/linthost/rules_unicorn_prefer_bigint_literals.go +42 -42
- package/linthost/rules_unicorn_prefer_blob_reading_methods.go +15 -15
- package/linthost/rules_unicorn_prefer_class_fields.go +57 -57
- package/linthost/rules_unicorn_prefer_classlist_toggle.go +76 -76
- package/linthost/rules_unicorn_prefer_code_point.go +21 -21
- package/linthost/rules_unicorn_prefer_date_now.go +47 -47
- package/linthost/rules_unicorn_prefer_default_parameters.go +96 -96
- package/linthost/rules_unicorn_prefer_dom_node_append.go +14 -14
- package/linthost/rules_unicorn_prefer_dom_node_dataset.go +27 -27
- package/linthost/rules_unicorn_prefer_dom_node_remove.go +17 -17
- package/linthost/rules_unicorn_prefer_dom_node_text_content.go +11 -11
- package/linthost/rules_unicorn_prefer_event_target.go +10 -10
- package/linthost/rules_unicorn_prefer_export_from.go +99 -99
- package/linthost/rules_unicorn_prefer_global_this.go +91 -91
- package/linthost/rules_unicorn_prefer_import_meta_properties.go +38 -38
- package/linthost/rules_unicorn_prefer_includes.go +108 -108
- package/linthost/rules_unicorn_prefer_json_parse_buffer.go +37 -37
- package/linthost/rules_unicorn_prefer_keyboard_event_key.go +10 -10
- package/linthost/rules_unicorn_prefer_logical_operator_over_ternary.go +20 -20
- package/linthost/rules_unicorn_prefer_math_min_max.go +33 -33
- package/linthost/rules_unicorn_prefer_math_trunc.go +32 -32
- package/linthost/rules_unicorn_prefer_modern_dom_apis.go +18 -18
- package/linthost/rules_unicorn_prefer_modern_math_apis.go +38 -38
- package/linthost/rules_unicorn_prefer_module.go +22 -22
- package/linthost/rules_unicorn_prefer_native_coercion_functions.go +73 -73
- package/linthost/rules_unicorn_prefer_negative_index.go +46 -46
- package/linthost/rules_unicorn_prefer_node_protocol.go +90 -90
- package/linthost/rules_unicorn_prefer_number_properties.go +57 -57
- package/linthost/rules_unicorn_prefer_object_from_entries.go +26 -26
- package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
- package/linthost/rules_unicorn_prefer_prototype_methods.go +25 -25
- package/linthost/rules_unicorn_prefer_query_selector.go +25 -25
- package/linthost/rules_unicorn_prefer_reflect_apply.go +10 -10
- package/linthost/rules_unicorn_prefer_regexp_test_rule.go +47 -47
- package/linthost/rules_unicorn_prefer_response_static_json.go +26 -26
- package/linthost/rules_unicorn_prefer_set_has.go +16 -16
- package/linthost/rules_unicorn_prefer_set_size.go +22 -22
- package/linthost/rules_unicorn_prefer_simple_condition_first.go +3 -3
- package/linthost/rules_unicorn_prefer_single_call.go +59 -59
- package/linthost/rules_unicorn_prefer_spread.go +24 -24
- package/linthost/rules_unicorn_prefer_string_raw.go +12 -12
- package/linthost/rules_unicorn_prefer_string_replace_all.go +46 -46
- package/linthost/rules_unicorn_prefer_string_slice.go +17 -17
- package/linthost/rules_unicorn_prefer_string_starts_ends_with.go +104 -104
- package/linthost/rules_unicorn_prefer_string_trim_start_end.go +21 -21
- package/linthost/rules_unicorn_prefer_structured_clone.go +36 -36
- package/linthost/rules_unicorn_prefer_switch.go +65 -65
- package/linthost/rules_unicorn_prefer_ternary.go +35 -35
- package/linthost/rules_unicorn_prefer_top_level_await.go +34 -34
- package/linthost/rules_unicorn_prefer_type_error.go +84 -84
- package/linthost/rules_unicorn_prevent_abbreviations.go +68 -68
- package/linthost/rules_unicorn_relative_url_style.go +19 -19
- package/linthost/rules_unicorn_require_array_join_separator.go +18 -18
- package/linthost/rules_unicorn_require_module_attributes.go +25 -25
- package/linthost/rules_unicorn_require_module_specifiers.go +28 -28
- package/linthost/rules_unicorn_require_number_to_fixed_digits_argument.go +18 -18
- package/linthost/rules_unicorn_require_post_message_target_origin.go +18 -18
- package/linthost/rules_unicorn_string_content.go +1 -1
- package/linthost/rules_unicorn_switch_case_braces.go +16 -16
- package/linthost/rules_unicorn_switch_case_break_position.go +3 -3
- package/linthost/rules_unicorn_template_indent.go +1 -1
- package/linthost/rules_unicorn_text_encoding_identifier_case.go +36 -36
- package/linthost/rules_unicorn_throw_new_error.go +30 -30
- package/linthost/rules_var.go +273 -3
- package/package.json +3 -3
- package/src/defaultFormat.ts +11 -7
- package/src/structures/ITtscLintConfig.ts +5 -5
- package/src/structures/TtscLintRuleSetting.ts +19 -19
- package/src/structures/{ITtscLintFormat.ts → format/ITtscLintFormat.ts} +48 -75
- package/src/structures/format/ITtscLintFormatJsDoc.ts +16 -0
- package/src/structures/format/ITtscLintFormatSortImports.ts +62 -0
- package/src/structures/format/index.ts +3 -0
- package/src/structures/index.ts +1 -1
- package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +20 -31
- package/src/structures/rules/ITtscLintBoundariesRules.ts +32 -36
- package/src/structures/rules/ITtscLintContributorRules.ts +6 -7
- package/src/structures/rules/ITtscLintCoreRules.ts +380 -408
- package/src/structures/rules/ITtscLintCypressRuleOptions.ts +5 -5
- package/src/structures/rules/ITtscLintCypressRules.ts +34 -38
- package/src/structures/rules/ITtscLintFunctionalRuleOptions.ts +28 -41
- package/src/structures/rules/ITtscLintFunctionalRules.ts +51 -56
- package/src/structures/rules/ITtscLintJestRules.ts +63 -74
- package/src/structures/rules/ITtscLintJsdocRules.ts +47 -54
- package/src/structures/rules/ITtscLintJsxA11yRules.ts +94 -102
- package/src/structures/rules/ITtscLintNextjsRules.ts +50 -55
- package/src/structures/rules/ITtscLintPlaywrightRules.ts +104 -122
- package/src/structures/rules/ITtscLintPromiseRules.ts +62 -71
- package/src/structures/rules/ITtscLintReactPerfRuleOptions.ts +4 -4
- package/src/structures/rules/ITtscLintReactPerfRules.ts +17 -19
- package/src/structures/rules/ITtscLintReactRuleOptions.ts +8 -10
- package/src/structures/rules/ITtscLintReactRules.ts +91 -104
- package/src/structures/rules/ITtscLintRegexpRules.ts +46 -54
- package/src/structures/rules/ITtscLintRuleOptionsMap.ts +7 -8
- package/src/structures/rules/ITtscLintSecurityRules.ts +45 -52
- package/src/structures/rules/ITtscLintSolidRules.ts +53 -56
- package/src/structures/rules/ITtscLintStorybookRuleOptions.ts +3 -4
- package/src/structures/rules/ITtscLintStorybookRules.ts +60 -69
- package/src/structures/rules/ITtscLintTanstackQueryRules.ts +31 -37
- package/src/structures/rules/ITtscLintTestingLibraryRuleOptions.ts +2 -3
- package/src/structures/rules/ITtscLintTestingLibraryRules.ts +102 -119
- package/src/structures/rules/ITtscLintTypeScriptRules.ts +460 -518
- package/src/structures/rules/ITtscLintVitestRules.ts +43 -51
- package/lib/structures/ITtscLintFormat.js.map +0 -1
- /package/lib/structures/{ITtscLintFormat.js → format/ITtscLintFormat.js} +0 -0
|
@@ -9,50 +9,58 @@ import (
|
|
|
9
9
|
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
10
10
|
)
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
const
|
|
12
|
+
// Group-order placeholders accepted inside the `order` option. A single
|
|
13
|
+
// declarative array expresses group order, blank-line separation, and special
|
|
14
|
+
// groups all by position.
|
|
15
|
+
const (
|
|
16
|
+
// builtinModulesPlaceholder buckets Node built-in modules (`fs`,
|
|
17
|
+
// `node:path`, `fs/promises`, ...).
|
|
18
|
+
builtinModulesPlaceholder = "<BUILTIN_MODULES>"
|
|
19
|
+
// thirdPartyModulesPlaceholder absorbs any specifier that matched no
|
|
20
|
+
// earlier explicit group.
|
|
21
|
+
thirdPartyModulesPlaceholder = "<THIRD_PARTY_MODULES>"
|
|
22
|
+
// typesPlaceholderPrefix marks the group of `import type` declarations.
|
|
23
|
+
// A regex may follow (`<TYPES>^[.]`) to scope the group to matching
|
|
24
|
+
// specifiers.
|
|
25
|
+
typesPlaceholderPrefix = "<TYPES>"
|
|
26
|
+
// separatorPlaceholder ("") emits one blank line at its position.
|
|
27
|
+
separatorPlaceholder = ""
|
|
28
|
+
)
|
|
16
29
|
|
|
17
|
-
// formatSortImportsOptions mirrors `
|
|
30
|
+
// formatSortImportsOptions mirrors `ITtscLintFormatSortImports`.
|
|
18
31
|
type formatSortImportsOptions struct {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
ImportOrderCaseInsensitive bool `json:"importOrderCaseInsensitive"`
|
|
32
|
+
Order []string `json:"order"`
|
|
33
|
+
CaseSensitive bool `json:"caseSensitive"`
|
|
34
|
+
CombineTypeAndValue bool `json:"combineTypeAndValue"`
|
|
23
35
|
}
|
|
24
36
|
|
|
25
|
-
// defaultImportOrder
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
// defaultImportOrder is used when the user supplies no `order`: Node built-ins,
|
|
38
|
+
// then the third-party catch-all, then relative imports. No `""` entries, so
|
|
39
|
+
// the default emits no blank lines between groups (blank lines are opt-in by
|
|
40
|
+
// position).
|
|
41
|
+
var defaultImportOrder = []string{
|
|
42
|
+
builtinModulesPlaceholder,
|
|
43
|
+
thirdPartyModulesPlaceholder,
|
|
44
|
+
`^[.]`,
|
|
45
|
+
}
|
|
31
46
|
|
|
32
47
|
// formatSortImports orders the file's top-level import declarations into
|
|
33
|
-
// canonical groups
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// 1. External modules (specifier does not start with `.`).
|
|
39
|
-
// 2. Relative modules (specifier starts with `.`).
|
|
48
|
+
// canonical groups, alphabetizes each group, merges duplicate imports of the
|
|
49
|
+
// same module, and (when `combineTypeAndValue` is on) folds a type-only
|
|
50
|
+
// import into a value import of the same module. Groups are user-configurable
|
|
51
|
+
// via the `order` option; when omitted, the rule falls back to {@link
|
|
52
|
+
// defaultImportOrder}.
|
|
40
53
|
//
|
|
41
|
-
//
|
|
42
|
-
// `
|
|
43
|
-
//
|
|
44
|
-
// when `importOrderCaseInsensitive: true`). Named specifiers inside each
|
|
45
|
-
// declaration are alphabetized when `importOrderSortSpecifiers` is true
|
|
46
|
-
// (the default).
|
|
54
|
+
// Within each group, declarations are sorted by their module-specifier text
|
|
55
|
+
// (ASCII order, or case-insensitive unless `caseSensitive: true`). Named
|
|
56
|
+
// specifiers inside each declaration are always sorted.
|
|
47
57
|
//
|
|
48
58
|
// Safety policy: if any byte between the contiguous imports is not
|
|
49
59
|
// whitespace, the rule bails. Comments anchored to specific imports would
|
|
50
60
|
// otherwise move with the wrong declaration, which is a strictly worse
|
|
51
|
-
// outcome than declining to sort.
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
// (`import * as ns from`), default imports, and `import type` all
|
|
55
|
-
// participate in the same group based on their module specifier.
|
|
61
|
+
// outcome than declining to sort. Side-effect imports (`import "foo"`) also
|
|
62
|
+
// bail the whole block because their evaluation order can carry meaning the
|
|
63
|
+
// rule cannot reason about.
|
|
56
64
|
type formatSortImports struct{}
|
|
57
65
|
|
|
58
66
|
func (formatSortImports) Name() string { return "format/sort-imports" }
|
|
@@ -80,7 +88,7 @@ func (formatSortImports) Check(ctx *Context, node *shimast.Node) {
|
|
|
80
88
|
// its module's top-level code for its observable effect; sorting it
|
|
81
89
|
// across a sibling import that depends on the polyfill being
|
|
82
90
|
// initialized first would silently change runtime behavior. The
|
|
83
|
-
// rule conservatively refuses to
|
|
91
|
+
// rule conservatively refuses to reorder the entire block in that
|
|
84
92
|
// case.
|
|
85
93
|
if len(imports) >= 2 &&
|
|
86
94
|
leadingTriviaIsAllWhitespace(src, imports) &&
|
|
@@ -101,11 +109,8 @@ func (formatSortImports) Check(ctx *Context, node *shimast.Node) {
|
|
|
101
109
|
return
|
|
102
110
|
}
|
|
103
111
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
for _, decl := range collectLeadingImports(statements.Nodes) {
|
|
108
|
-
reportNamedSpecifierSort(ctx, decl, opts.caseInsensitive)
|
|
112
|
+
for _, decl := range imports {
|
|
113
|
+
reportNamedSpecifierSort(ctx, decl, opts.caseSensitive)
|
|
109
114
|
}
|
|
110
115
|
}
|
|
111
116
|
|
|
@@ -113,63 +118,93 @@ func (formatSortImports) Check(ctx *Context, node *shimast.Node) {
|
|
|
113
118
|
// during one Check call. All option defaults are applied here so the
|
|
114
119
|
// rest of the rule code does not branch on nil-ness.
|
|
115
120
|
type resolvedSortImportsOptions struct {
|
|
116
|
-
groups
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
caseInsensitive bool
|
|
121
|
+
groups []sortImportsGroup
|
|
122
|
+
caseSensitive bool
|
|
123
|
+
combineTypeAndValue bool
|
|
120
124
|
}
|
|
121
125
|
|
|
126
|
+
// sortImportsGroup is one resolved entry of the `order` array. A separator
|
|
127
|
+
// entry ("") is not a real group; it is folded into the following group's
|
|
128
|
+
// sepBefore flag during parsing and never appears in the resolved list.
|
|
122
129
|
type sortImportsGroup struct {
|
|
123
130
|
raw string
|
|
124
|
-
pattern *regexp.Regexp
|
|
131
|
+
pattern *regexp.Regexp // nil for placeholder groups without a regex
|
|
125
132
|
thirdParty bool
|
|
133
|
+
builtin bool
|
|
134
|
+
typesOnly bool // <TYPES> group: matches `import type` declarations
|
|
135
|
+
sepBefore bool // a "" preceded this group in the `order` array
|
|
126
136
|
}
|
|
127
137
|
|
|
128
138
|
func loadSortImportsOptions(ctx *Context) resolvedSortImportsOptions {
|
|
129
139
|
var raw formatSortImportsOptions
|
|
130
140
|
_ = ctx.DecodeOptions(&raw)
|
|
131
|
-
order := raw.
|
|
141
|
+
order := raw.Order
|
|
132
142
|
if len(order) == 0 {
|
|
133
143
|
order = defaultImportOrder
|
|
134
144
|
}
|
|
145
|
+
groups := parseImportOrder(order)
|
|
146
|
+
return resolvedSortImportsOptions{
|
|
147
|
+
groups: groups,
|
|
148
|
+
caseSensitive: raw.CaseSensitive,
|
|
149
|
+
combineTypeAndValue: raw.CombineTypeAndValue,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// parseImportOrder turns the raw `order` array into resolved groups. "" entries
|
|
154
|
+
// fold into the next group's sepBefore flag. A third-party catch-all is
|
|
155
|
+
// injected at the front when the user omitted one so unmatched specifiers
|
|
156
|
+
// always land in a real group rather than after every explicit one.
|
|
157
|
+
func parseImportOrder(order []string) []sortImportsGroup {
|
|
135
158
|
groups := make([]sortImportsGroup, 0, len(order))
|
|
159
|
+
pendingSep := false
|
|
136
160
|
for _, pat := range order {
|
|
137
|
-
if pat ==
|
|
138
|
-
|
|
161
|
+
if pat == separatorPlaceholder {
|
|
162
|
+
pendingSep = true
|
|
139
163
|
continue
|
|
140
164
|
}
|
|
141
|
-
|
|
142
|
-
if
|
|
165
|
+
group, ok := compileImportOrderGroup(pat)
|
|
166
|
+
if !ok {
|
|
143
167
|
// Bad regex: skip the group rather than failing the entire run.
|
|
144
|
-
//
|
|
145
|
-
//
|
|
168
|
+
// Users see "no match" behavior instead of a crash; a stray
|
|
169
|
+
// separator in front of the skipped group carries to the next.
|
|
146
170
|
continue
|
|
147
171
|
}
|
|
148
|
-
|
|
172
|
+
group.sepBefore = pendingSep
|
|
173
|
+
pendingSep = false
|
|
174
|
+
groups = append(groups, group)
|
|
149
175
|
}
|
|
150
|
-
if len(groups) == 0 {
|
|
151
|
-
// Empty `importOrder` collapses to the third-party catchall so the
|
|
152
|
-
// rule still produces a deterministic ordering.
|
|
153
|
-
groups = []sortImportsGroup{{raw: thirdPartyModulesPlaceholder, thirdParty: true}}
|
|
154
|
-
}
|
|
155
|
-
// Ensure a third-party catchall exists; if the user omitted it,
|
|
156
|
-
// append one so unmatched specifiers don't land in a phantom group.
|
|
157
176
|
if !hasThirdPartyGroup(groups) {
|
|
158
|
-
groups = append(
|
|
159
|
-
}
|
|
160
|
-
separation := true
|
|
161
|
-
if raw.ImportOrderSeparation != nil {
|
|
162
|
-
separation = *raw.ImportOrderSeparation
|
|
177
|
+
groups = append([]sortImportsGroup{{raw: thirdPartyModulesPlaceholder, thirdParty: true}}, groups...)
|
|
163
178
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
179
|
+
return groups
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// compileImportOrderGroup resolves one non-separator `order` entry into a
|
|
183
|
+
// group. The bool is false only when a regex entry fails to compile.
|
|
184
|
+
func compileImportOrderGroup(pat string) (sortImportsGroup, bool) {
|
|
185
|
+
switch {
|
|
186
|
+
case pat == builtinModulesPlaceholder:
|
|
187
|
+
return sortImportsGroup{raw: pat, builtin: true}, true
|
|
188
|
+
case pat == thirdPartyModulesPlaceholder:
|
|
189
|
+
return sortImportsGroup{raw: pat, thirdParty: true}, true
|
|
190
|
+
case strings.HasPrefix(pat, typesPlaceholderPrefix):
|
|
191
|
+
rest := strings.TrimPrefix(pat, typesPlaceholderPrefix)
|
|
192
|
+
group := sortImportsGroup{raw: pat, typesOnly: true}
|
|
193
|
+
if rest == "" {
|
|
194
|
+
return group, true
|
|
195
|
+
}
|
|
196
|
+
compiled, err := regexp.Compile(rest)
|
|
197
|
+
if err != nil {
|
|
198
|
+
return sortImportsGroup{}, false
|
|
199
|
+
}
|
|
200
|
+
group.pattern = compiled
|
|
201
|
+
return group, true
|
|
202
|
+
default:
|
|
203
|
+
compiled, err := regexp.Compile(pat)
|
|
204
|
+
if err != nil {
|
|
205
|
+
return sortImportsGroup{}, false
|
|
206
|
+
}
|
|
207
|
+
return sortImportsGroup{raw: pat, pattern: compiled}, true
|
|
173
208
|
}
|
|
174
209
|
}
|
|
175
210
|
|
|
@@ -184,32 +219,64 @@ func hasThirdPartyGroup(groups []sortImportsGroup) bool {
|
|
|
184
219
|
return false
|
|
185
220
|
}
|
|
186
221
|
|
|
187
|
-
// matchGroup returns the index of the first group that claims
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
|
|
222
|
+
// matchGroup returns the index of the first group that claims a declaration
|
|
223
|
+
// with the given specifier and type-only flag. Groups are checked in order, so
|
|
224
|
+
// a `<TYPES>` group placed ahead of a regex group claims type-only imports
|
|
225
|
+
// first; the third-party catchall absorbs anything left over.
|
|
226
|
+
func matchGroup(groups []sortImportsGroup, specifier string, typeOnly bool) int {
|
|
191
227
|
thirdPartyIdx := -1
|
|
192
228
|
for i, g := range groups {
|
|
193
|
-
|
|
229
|
+
switch {
|
|
230
|
+
case g.thirdParty:
|
|
194
231
|
if thirdPartyIdx < 0 {
|
|
195
232
|
thirdPartyIdx = i
|
|
196
233
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
234
|
+
case g.builtin:
|
|
235
|
+
if isBuiltinModule(specifier) {
|
|
236
|
+
return i
|
|
237
|
+
}
|
|
238
|
+
case g.typesOnly:
|
|
239
|
+
if typeOnly && (g.pattern == nil || g.pattern.MatchString(specifier)) {
|
|
240
|
+
return i
|
|
241
|
+
}
|
|
242
|
+
case g.pattern != nil && g.pattern.MatchString(specifier):
|
|
200
243
|
return i
|
|
201
244
|
}
|
|
202
245
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
246
|
+
return thirdPartyIdx
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// nodeBuiltinModules is the set of Node.js built-in module names recognized by
|
|
250
|
+
// the `<BUILTIN_MODULES>` group. A `node:` prefix and a `pkg/subpath` suffix
|
|
251
|
+
// are both stripped before the lookup.
|
|
252
|
+
var nodeBuiltinModules = map[string]struct{}{
|
|
253
|
+
"assert": {}, "async_hooks": {}, "buffer": {}, "child_process": {},
|
|
254
|
+
"cluster": {}, "console": {}, "constants": {}, "crypto": {},
|
|
255
|
+
"dgram": {}, "diagnostics_channel": {}, "dns": {}, "domain": {},
|
|
256
|
+
"events": {}, "fs": {}, "http": {}, "http2": {}, "https": {},
|
|
257
|
+
"inspector": {}, "module": {}, "net": {}, "os": {}, "path": {},
|
|
258
|
+
"perf_hooks": {}, "process": {}, "punycode": {}, "querystring": {},
|
|
259
|
+
"readline": {}, "repl": {}, "stream": {}, "string_decoder": {},
|
|
260
|
+
"sys": {}, "test": {}, "timers": {}, "tls": {}, "trace_events": {},
|
|
261
|
+
"tty": {}, "url": {}, "util": {}, "v8": {}, "vm": {}, "wasi": {},
|
|
262
|
+
"worker_threads": {}, "zlib": {},
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// isBuiltinModule reports whether the specifier names a Node.js built-in
|
|
266
|
+
// module. `node:fs`, `fs`, and `fs/promises` all qualify.
|
|
267
|
+
func isBuiltinModule(specifier string) bool {
|
|
268
|
+
name := strings.TrimPrefix(specifier, "node:")
|
|
269
|
+
if slash := strings.IndexByte(name, '/'); slash >= 0 {
|
|
270
|
+
name = name[:slash]
|
|
271
|
+
}
|
|
272
|
+
_, ok := nodeBuiltinModules[name]
|
|
273
|
+
return ok
|
|
207
274
|
}
|
|
208
275
|
|
|
209
276
|
// specifierListHasCommentTrivia returns true when the source between
|
|
210
277
|
// adjacent specifiers contains any byte that is not whitespace,
|
|
211
278
|
// comma, or whitespace-equivalent. A `/* x */` or `// x` inside the
|
|
212
|
-
// list anchors the rule into a no-op
|
|
279
|
+
// list anchors the rule into a no-op, sorting would re-emit the
|
|
213
280
|
// specifier texts joined with `", "` and silently drop the comment.
|
|
214
281
|
func specifierListHasCommentTrivia(src string, specifiers []*shimast.Node) bool {
|
|
215
282
|
for i := 1; i < len(specifiers); i++ {
|
|
@@ -284,29 +351,62 @@ func leadingTriviaIsAllWhitespace(src string, imports []*shimast.Node) bool {
|
|
|
284
351
|
return true
|
|
285
352
|
}
|
|
286
353
|
|
|
287
|
-
//
|
|
288
|
-
// the
|
|
354
|
+
// siSpec is one named import specifier captured for merge/sort: its local
|
|
355
|
+
// name drives the sort key, its source text is re-emitted verbatim, and the
|
|
356
|
+
// type-only flag of the owning declaration drives `combineTypeAndValue`
|
|
357
|
+
// promotion.
|
|
358
|
+
type siSpec struct {
|
|
359
|
+
sortKey string
|
|
360
|
+
text string
|
|
361
|
+
fromTypeOnlyDecl bool
|
|
362
|
+
// inlineType is the specifier's own AST `type` modifier (`import { type Foo }`),
|
|
363
|
+
// distinct from fromTypeOnlyDecl (the whole `import type { … }` declaration).
|
|
364
|
+
// It classifies a specifier without re-parsing its text, so a binding literally
|
|
365
|
+
// named `type` (`import { type as bar }` -> name `type`, alias `bar`, NOT a type
|
|
366
|
+
// specifier) is not mistaken for a type modifier by a `"type "` string prefix.
|
|
367
|
+
inlineType bool
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// siDecl is the parsed shape of one import declaration used by the block
|
|
371
|
+
// rebuilder. Declarations with a namespace binding are not mergeable and are
|
|
372
|
+
// re-emitted from their original text.
|
|
373
|
+
type siDecl struct {
|
|
374
|
+
specifier string
|
|
375
|
+
specRaw string
|
|
376
|
+
typeOnly bool
|
|
377
|
+
defaultName string
|
|
378
|
+
named []siSpec
|
|
379
|
+
namespace bool
|
|
380
|
+
// hasSpecComment marks a declaration whose named-import braces carry a comment.
|
|
381
|
+
// The merge rebuilder joins specifier texts with ", " and would drop such a
|
|
382
|
+
// comment, so a flagged declaration is made unmergeable (like a namespace
|
|
383
|
+
// binding) and re-emitted from its original text. See mergeKey.
|
|
384
|
+
hasSpecComment bool
|
|
385
|
+
original string
|
|
386
|
+
semicolon bool
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// siEntry is a (possibly merged) import declaration ready to be grouped,
|
|
390
|
+
// sorted, and emitted.
|
|
391
|
+
type siEntry struct {
|
|
392
|
+
group int
|
|
393
|
+
specifier string
|
|
394
|
+
sortKey string
|
|
395
|
+
typeOnly bool
|
|
396
|
+
text string
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// buildSortedImportBlock returns the canonical group/sort/merge representation
|
|
400
|
+
// of the contiguous import declarations.
|
|
289
401
|
func buildSortedImportBlock(src string, imports []*shimast.Node, opts resolvedSortImportsOptions) string {
|
|
290
|
-
|
|
291
|
-
group int
|
|
292
|
-
specifier string
|
|
293
|
-
sortKey string
|
|
294
|
-
text string
|
|
295
|
-
}
|
|
296
|
-
entries := make([]entry, 0, len(imports))
|
|
402
|
+
decls := make([]siDecl, 0, len(imports))
|
|
297
403
|
for _, decl := range imports {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
entries = append(entries, entry{
|
|
305
|
-
group: matchGroup(opts.groups, spec),
|
|
306
|
-
specifier: spec,
|
|
307
|
-
sortKey: sortKey,
|
|
308
|
-
text: text,
|
|
309
|
-
})
|
|
404
|
+
decls = append(decls, parseImportDecl(src, decl))
|
|
405
|
+
}
|
|
406
|
+
entries := mergeImportDecls(decls, opts)
|
|
407
|
+
for i := range entries {
|
|
408
|
+
entries[i].group = matchGroup(opts.groups, entries[i].specifier, entries[i].typeOnly)
|
|
409
|
+
entries[i].sortKey = foldCase(entries[i].specifier, opts.caseSensitive)
|
|
310
410
|
}
|
|
311
411
|
sort.SliceStable(entries, func(i, j int) bool {
|
|
312
412
|
if entries[i].group != entries[j].group {
|
|
@@ -319,7 +419,7 @@ func buildSortedImportBlock(src string, imports []*shimast.Node, opts resolvedSo
|
|
|
319
419
|
prevGroup := entries[0].group
|
|
320
420
|
for i, e := range entries {
|
|
321
421
|
if i > 0 {
|
|
322
|
-
if e.group != prevGroup && opts.
|
|
422
|
+
if e.group != prevGroup && separatorBetween(opts.groups, prevGroup, e.group) {
|
|
323
423
|
b.WriteString("\n\n")
|
|
324
424
|
} else {
|
|
325
425
|
b.WriteString("\n")
|
|
@@ -331,6 +431,276 @@ func buildSortedImportBlock(src string, imports []*shimast.Node, opts resolvedSo
|
|
|
331
431
|
return b.String()
|
|
332
432
|
}
|
|
333
433
|
|
|
434
|
+
// separatorBetween reports whether a blank line should sit between two
|
|
435
|
+
// consecutive (in sorted output) groups. Groups skipped because they had no
|
|
436
|
+
// imports still contribute their sepBefore flag, so `[A, "", B]` with an empty
|
|
437
|
+
// A and B that both fall through to a later group collapses to one blank line.
|
|
438
|
+
func separatorBetween(groups []sortImportsGroup, prev, curr int) bool {
|
|
439
|
+
for k := prev + 1; k <= curr && k < len(groups); k++ {
|
|
440
|
+
if groups[k].sepBefore {
|
|
441
|
+
return true
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return false
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// parseImportDecl captures the merge-relevant shape of one import declaration.
|
|
448
|
+
func parseImportDecl(src string, decl *shimast.Node) siDecl {
|
|
449
|
+
out := siDecl{
|
|
450
|
+
specifier: moduleSpecifierText(decl),
|
|
451
|
+
specRaw: moduleSpecifierRaw(src, decl),
|
|
452
|
+
original: importStatementText(src, decl),
|
|
453
|
+
}
|
|
454
|
+
out.semicolon = strings.HasSuffix(out.original, ";")
|
|
455
|
+
imp := decl.AsImportDeclaration()
|
|
456
|
+
if imp == nil || imp.ImportClause == nil {
|
|
457
|
+
return out
|
|
458
|
+
}
|
|
459
|
+
clause := imp.ImportClause.AsImportClause()
|
|
460
|
+
if clause == nil {
|
|
461
|
+
return out
|
|
462
|
+
}
|
|
463
|
+
out.typeOnly = clause.PhaseModifier == shimast.KindTypeKeyword
|
|
464
|
+
out.defaultName = identifierText(clause.Name())
|
|
465
|
+
// A comment anywhere in the rebuilt import prefix (`import [type] [D] [, { … }]
|
|
466
|
+
// from `) would be lost when renderMergedDecl reconstructs the statement
|
|
467
|
+
// field-by-field. Flag it so mergeKey keeps the declaration unmergeable and
|
|
468
|
+
// its original text is preserved. This runs BEFORE the no-named-bindings /
|
|
469
|
+
// namespace early returns so a default-only import (`import a /* c */ from
|
|
470
|
+
// "m"`) is flagged too; it depends only on decl.Pos() and the module
|
|
471
|
+
// specifier, not on the named bindings. The prefix holds no module-path
|
|
472
|
+
// string, so a `//` or `/*` there is unambiguously a comment (a leading
|
|
473
|
+
// comment before `import` is excluded: SkipTrivia advances to `import`).
|
|
474
|
+
if imp.ModuleSpecifier != nil {
|
|
475
|
+
pStart := shimscanner.SkipTrivia(src, decl.Pos())
|
|
476
|
+
pEnd := shimscanner.SkipTrivia(src, imp.ModuleSpecifier.Pos())
|
|
477
|
+
if pStart >= 0 && pEnd <= len(src) && pStart < pEnd {
|
|
478
|
+
if span := src[pStart:pEnd]; strings.Contains(span, "//") || strings.Contains(span, "/*") {
|
|
479
|
+
out.hasSpecComment = true
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// Also the module-specifier -> `;` tail (`from "m" /* keep */;`): a block
|
|
483
|
+
// comment there is interior to the declaration but outside the
|
|
484
|
+
// inter-declaration gap that leadingTriviaIsAllWhitespace scans, so the
|
|
485
|
+
// merge rebuilder would drop it. The module string itself is excluded (the
|
|
486
|
+
// span starts at ModuleSpecifier.End()), so no `//`-in-a-path false positive.
|
|
487
|
+
if tStart, tEnd := imp.ModuleSpecifier.End(), decl.End(); tStart >= 0 && tEnd <= len(src) && tStart < tEnd {
|
|
488
|
+
if span := src[tStart:tEnd]; strings.Contains(span, "//") || strings.Contains(span, "/*") {
|
|
489
|
+
out.hasSpecComment = true
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
// An import-attributes clause (`with { type: "json" }` / `assert { … }`) is not
|
|
494
|
+
// reconstructed by renderMergedDecl, so merging would silently drop those
|
|
495
|
+
// bytes. Flag the declaration unmergeable (its original text is then re-emitted
|
|
496
|
+
// verbatim), mirroring the print-width import printer, which bails to verbatim
|
|
497
|
+
// on `imp.Attributes != nil` for the same reason.
|
|
498
|
+
if imp.Attributes != nil {
|
|
499
|
+
out.hasSpecComment = true
|
|
500
|
+
}
|
|
501
|
+
if clause.NamedBindings == nil {
|
|
502
|
+
return out
|
|
503
|
+
}
|
|
504
|
+
if clause.NamedBindings.Kind == shimast.KindNamespaceImport {
|
|
505
|
+
out.namespace = true
|
|
506
|
+
return out
|
|
507
|
+
}
|
|
508
|
+
if clause.NamedBindings.Kind != shimast.KindNamedImports {
|
|
509
|
+
return out
|
|
510
|
+
}
|
|
511
|
+
named := clause.NamedBindings.AsNamedImports()
|
|
512
|
+
if named == nil || named.Elements == nil {
|
|
513
|
+
return out
|
|
514
|
+
}
|
|
515
|
+
for _, spec := range named.Elements.Nodes {
|
|
516
|
+
s := spec.AsImportSpecifier()
|
|
517
|
+
if s == nil {
|
|
518
|
+
continue
|
|
519
|
+
}
|
|
520
|
+
start := shimscanner.SkipTrivia(src, spec.Pos())
|
|
521
|
+
out.named = append(out.named, siSpec{
|
|
522
|
+
sortKey: identifierText(s.Name()),
|
|
523
|
+
text: src[start:spec.End()],
|
|
524
|
+
fromTypeOnlyDecl: out.typeOnly,
|
|
525
|
+
inlineType: s.IsTypeOnly,
|
|
526
|
+
})
|
|
527
|
+
}
|
|
528
|
+
return out
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// mergeImportDecls collapses declarations of the same module into one entry.
|
|
532
|
+
// Same-module value imports always merge; same-module type-only imports always
|
|
533
|
+
// merge; a value import and a type-only import of the same module merge only
|
|
534
|
+
// when `combineTypeAndValue` is set. Namespace imports and merge conflicts fall
|
|
535
|
+
// back to one entry per original declaration.
|
|
536
|
+
func mergeImportDecls(decls []siDecl, opts resolvedSortImportsOptions) []siEntry {
|
|
537
|
+
order := make([]string, 0, len(decls))
|
|
538
|
+
buckets := make(map[string][]siDecl)
|
|
539
|
+
for _, d := range decls {
|
|
540
|
+
key := mergeKey(d, opts.combineTypeAndValue)
|
|
541
|
+
if _, seen := buckets[key]; !seen {
|
|
542
|
+
order = append(order, key)
|
|
543
|
+
}
|
|
544
|
+
buckets[key] = append(buckets[key], d)
|
|
545
|
+
}
|
|
546
|
+
entries := make([]siEntry, 0, len(decls))
|
|
547
|
+
for _, key := range order {
|
|
548
|
+
entries = append(entries, mergeBucket(buckets[key], opts)...)
|
|
549
|
+
}
|
|
550
|
+
return entries
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// mergeKey groups declarations that are eligible to merge. Namespace imports
|
|
554
|
+
// get a unique key (a per-declaration counter via the original text plus a
|
|
555
|
+
// sentinel) so they never merge.
|
|
556
|
+
func mergeKey(d siDecl, combine bool) string {
|
|
557
|
+
if d.namespace {
|
|
558
|
+
return "\x00ns\x00" + d.original
|
|
559
|
+
}
|
|
560
|
+
// A named-import list carrying a comment is unmergeable so the comment
|
|
561
|
+
// survives in the declaration's original text (a per-declaration key).
|
|
562
|
+
if d.hasSpecComment {
|
|
563
|
+
return "\x00cmt\x00" + d.original
|
|
564
|
+
}
|
|
565
|
+
if combine || !d.typeOnly {
|
|
566
|
+
return "v\x00" + d.specifier
|
|
567
|
+
}
|
|
568
|
+
return "t\x00" + d.specifier
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// mergeBucket renders a bucket of same-key declarations. A single declaration
|
|
572
|
+
// (or an unmergeable conflict) is emitted from its original text; otherwise the
|
|
573
|
+
// declarations fold into one rebuilt statement.
|
|
574
|
+
func mergeBucket(group []siDecl, opts resolvedSortImportsOptions) []siEntry {
|
|
575
|
+
if len(group) == 1 {
|
|
576
|
+
return []siEntry{originalEntry(group[0])}
|
|
577
|
+
}
|
|
578
|
+
text, typeOnly, ok := renderMergedDecl(group, opts)
|
|
579
|
+
if !ok {
|
|
580
|
+
out := make([]siEntry, 0, len(group))
|
|
581
|
+
for _, d := range group {
|
|
582
|
+
out = append(out, originalEntry(d))
|
|
583
|
+
}
|
|
584
|
+
return out
|
|
585
|
+
}
|
|
586
|
+
return []siEntry{{specifier: group[0].specifier, typeOnly: typeOnly, text: text}}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// originalEntry wraps one declaration's untouched source text as an entry.
|
|
590
|
+
func originalEntry(d siDecl) siEntry {
|
|
591
|
+
return siEntry{specifier: d.specifier, typeOnly: d.typeOnly, text: d.original}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// renderMergedDecl folds a bucket of mergeable declarations into one statement.
|
|
595
|
+
// It returns the rendered text, whether the merged result is a type-only import,
|
|
596
|
+
// and an `ok` flag that is false when the declarations cannot be merged
|
|
597
|
+
// (conflicting default names, or a type-only default that cannot survive in a
|
|
598
|
+
// mixed value import).
|
|
599
|
+
func renderMergedDecl(group []siDecl, opts resolvedSortImportsOptions) (string, bool, bool) {
|
|
600
|
+
mergedTypeOnly := true
|
|
601
|
+
for _, d := range group {
|
|
602
|
+
if !d.typeOnly {
|
|
603
|
+
mergedTypeOnly = false
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
defaultName := ""
|
|
607
|
+
defaultTypeOnly := false
|
|
608
|
+
for _, d := range group {
|
|
609
|
+
if d.defaultName == "" {
|
|
610
|
+
continue
|
|
611
|
+
}
|
|
612
|
+
if defaultName != "" && defaultName != d.defaultName {
|
|
613
|
+
return "", false, false
|
|
614
|
+
}
|
|
615
|
+
defaultName = d.defaultName
|
|
616
|
+
defaultTypeOnly = d.typeOnly
|
|
617
|
+
}
|
|
618
|
+
// A type-only default cannot be expressed inside a mixed value import
|
|
619
|
+
// (`import type Foo, { value }` would retype `value`).
|
|
620
|
+
if defaultName != "" && defaultTypeOnly && !mergedTypeOnly {
|
|
621
|
+
return "", false, false
|
|
622
|
+
}
|
|
623
|
+
specs := collectMergedSpecs(group, mergedTypeOnly, opts.caseSensitive)
|
|
624
|
+
if defaultName == "" && len(specs) == 0 {
|
|
625
|
+
return "", false, false
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
var b strings.Builder
|
|
629
|
+
b.WriteString("import ")
|
|
630
|
+
if mergedTypeOnly {
|
|
631
|
+
b.WriteString("type ")
|
|
632
|
+
}
|
|
633
|
+
if defaultName != "" {
|
|
634
|
+
b.WriteString(defaultName)
|
|
635
|
+
if len(specs) > 0 {
|
|
636
|
+
b.WriteString(", ")
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if len(specs) > 0 {
|
|
640
|
+
b.WriteString("{ ")
|
|
641
|
+
b.WriteString(strings.Join(specs, ", "))
|
|
642
|
+
b.WriteString(" }")
|
|
643
|
+
}
|
|
644
|
+
b.WriteString(" from ")
|
|
645
|
+
b.WriteString(group[0].specRaw)
|
|
646
|
+
if group[0].semicolon {
|
|
647
|
+
b.WriteString(";")
|
|
648
|
+
}
|
|
649
|
+
return b.String(), mergedTypeOnly, true
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// collectMergedSpecs gathers, de-duplicates, and sorts the named specifiers of
|
|
653
|
+
// a merged declaration. A specifier from a type-only declaration folded into a
|
|
654
|
+
// mixed value import gains an inline `type ` prefix. Specifiers are
|
|
655
|
+
// de-duplicated by local binding name — keeping the value form, since one
|
|
656
|
+
// binding cannot be both a value and a type import — and ordered value before
|
|
657
|
+
// type, then by name.
|
|
658
|
+
func collectMergedSpecs(group []siDecl, mergedTypeOnly, caseSensitive bool) []string {
|
|
659
|
+
type spec struct {
|
|
660
|
+
name string
|
|
661
|
+
isType bool
|
|
662
|
+
text string
|
|
663
|
+
}
|
|
664
|
+
index := make(map[string]int)
|
|
665
|
+
items := make([]spec, 0)
|
|
666
|
+
for _, d := range group {
|
|
667
|
+
for _, s := range d.named {
|
|
668
|
+
text := s.text
|
|
669
|
+
// Classify by AST flags, not a `"type "` text prefix. A binding literally
|
|
670
|
+
// named `type` (`import { type as bar }`) has inlineType=false and must stay
|
|
671
|
+
// a value specifier; a string-prefix check would mis-promote it to a type
|
|
672
|
+
// specifier and skip the modifier when folding a type-only declaration.
|
|
673
|
+
isType := s.inlineType
|
|
674
|
+
if s.fromTypeOnlyDecl && !mergedTypeOnly {
|
|
675
|
+
// A specifier from `import type { … }` carries no inline `type` modifier
|
|
676
|
+
// (TS forbids it there), so folding into a mixed value import must add one.
|
|
677
|
+
text = "type " + text
|
|
678
|
+
isType = true
|
|
679
|
+
}
|
|
680
|
+
cur := spec{name: s.sortKey, isType: isType, text: text}
|
|
681
|
+
if at, dup := index[s.sortKey]; dup {
|
|
682
|
+
if items[at].isType && !cur.isType {
|
|
683
|
+
items[at] = cur
|
|
684
|
+
}
|
|
685
|
+
continue
|
|
686
|
+
}
|
|
687
|
+
index[s.sortKey] = len(items)
|
|
688
|
+
items = append(items, cur)
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
sort.SliceStable(items, func(i, j int) bool {
|
|
692
|
+
if items[i].isType != items[j].isType {
|
|
693
|
+
return !items[i].isType
|
|
694
|
+
}
|
|
695
|
+
return foldCase(items[i].name, caseSensitive) < foldCase(items[j].name, caseSensitive)
|
|
696
|
+
})
|
|
697
|
+
texts := make([]string, len(items))
|
|
698
|
+
for i, it := range items {
|
|
699
|
+
texts[i] = it.text
|
|
700
|
+
}
|
|
701
|
+
return texts
|
|
702
|
+
}
|
|
703
|
+
|
|
334
704
|
// importStatementText returns the import declaration's source text,
|
|
335
705
|
// stripped of any leading whitespace/comment trivia.
|
|
336
706
|
func importStatementText(src string, decl *shimast.Node) string {
|
|
@@ -355,17 +725,43 @@ func moduleSpecifierText(decl *shimast.Node) string {
|
|
|
355
725
|
return stringLiteralText(imp.ModuleSpecifier)
|
|
356
726
|
}
|
|
357
727
|
|
|
358
|
-
//
|
|
359
|
-
//
|
|
728
|
+
// moduleSpecifierRaw returns the module specifier's source text including its
|
|
729
|
+
// surrounding quotes, so a merged declaration preserves the original quote
|
|
730
|
+
// style.
|
|
731
|
+
func moduleSpecifierRaw(src string, decl *shimast.Node) string {
|
|
732
|
+
imp := decl.AsImportDeclaration()
|
|
733
|
+
if imp == nil || imp.ModuleSpecifier == nil {
|
|
734
|
+
return ""
|
|
735
|
+
}
|
|
736
|
+
start := shimscanner.SkipTrivia(src, imp.ModuleSpecifier.Pos())
|
|
737
|
+
end := imp.ModuleSpecifier.End()
|
|
738
|
+
if start < 0 || start > end || end > len(src) {
|
|
739
|
+
return ""
|
|
740
|
+
}
|
|
741
|
+
return src[start:end]
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// foldCase lowercases `s` unless case-sensitive ordering was requested.
|
|
745
|
+
func foldCase(s string, caseSensitive bool) string {
|
|
746
|
+
if caseSensitive {
|
|
747
|
+
return s
|
|
748
|
+
}
|
|
749
|
+
return strings.ToLower(s)
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// specifierEntry is the sortable record captured for each named import
|
|
753
|
+
// specifier: the local name used as the sort key, whether it is an inline
|
|
754
|
+
// `type` specifier (those sort after value specifiers), and the literal source
|
|
360
755
|
// text that gets re-emitted in canonical order.
|
|
361
756
|
type specifierEntry struct {
|
|
362
|
-
key
|
|
363
|
-
|
|
757
|
+
key string
|
|
758
|
+
isType bool
|
|
759
|
+
text string
|
|
364
760
|
}
|
|
365
761
|
|
|
366
762
|
// reportNamedSpecifierSort reports a fix when an import's `{ a, b }` list
|
|
367
763
|
// is out of alphabetical order. Type-only specifiers participate in the
|
|
368
|
-
// same sort key as value specifiers
|
|
764
|
+
// same sort key as value specifiers, prettier's plugin-sort-imports
|
|
369
765
|
// matches this behavior.
|
|
370
766
|
//
|
|
371
767
|
// Safety policy: comment trivia between specifiers anchors the rule
|
|
@@ -373,7 +769,7 @@ type specifierEntry struct {
|
|
|
373
769
|
// would discard any `/* x */` comment carried inside the list. The
|
|
374
770
|
// block-level sort applies the same policy via
|
|
375
771
|
// `leadingTriviaIsAllWhitespace`; this is the per-specifier analog.
|
|
376
|
-
func reportNamedSpecifierSort(ctx *Context, decl *shimast.Node,
|
|
772
|
+
func reportNamedSpecifierSort(ctx *Context, decl *shimast.Node, caseSensitive bool) {
|
|
377
773
|
imp := decl.AsImportDeclaration()
|
|
378
774
|
if imp == nil || imp.ImportClause == nil {
|
|
379
775
|
return
|
|
@@ -410,13 +806,10 @@ func reportNamedSpecifierSort(ctx *Context, decl *shimast.Node, caseInsensitive
|
|
|
410
806
|
}
|
|
411
807
|
start := shimscanner.SkipTrivia(src, spec.Pos())
|
|
412
808
|
end := spec.End()
|
|
413
|
-
key := name
|
|
414
|
-
if caseInsensitive {
|
|
415
|
-
key = strings.ToLower(name)
|
|
416
|
-
}
|
|
417
809
|
entries = append(entries, specifierEntry{
|
|
418
|
-
key:
|
|
419
|
-
|
|
810
|
+
key: foldCase(name, caseSensitive),
|
|
811
|
+
isType: s.IsTypeOnly,
|
|
812
|
+
text: src[start:end],
|
|
420
813
|
})
|
|
421
814
|
}
|
|
422
815
|
if len(entries) < 2 {
|
|
@@ -425,11 +818,14 @@ func reportNamedSpecifierSort(ctx *Context, decl *shimast.Node, caseInsensitive
|
|
|
425
818
|
sorted := make([]specifierEntry, len(entries))
|
|
426
819
|
copy(sorted, entries)
|
|
427
820
|
sort.SliceStable(sorted, func(i, j int) bool {
|
|
821
|
+
if sorted[i].isType != sorted[j].isType {
|
|
822
|
+
return !sorted[i].isType
|
|
823
|
+
}
|
|
428
824
|
return sorted[i].key < sorted[j].key
|
|
429
825
|
})
|
|
430
826
|
changed := false
|
|
431
827
|
for i := range entries {
|
|
432
|
-
if entries[i].key != sorted[i].key {
|
|
828
|
+
if entries[i].key != sorted[i].key || entries[i].isType != sorted[i].isType {
|
|
433
829
|
changed = true
|
|
434
830
|
break
|
|
435
831
|
}
|