@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
package/linthost/format.go
CHANGED
|
@@ -5,6 +5,7 @@ import (
|
|
|
5
5
|
"fmt"
|
|
6
6
|
"os"
|
|
7
7
|
"sort"
|
|
8
|
+
"sync"
|
|
8
9
|
)
|
|
9
10
|
|
|
10
11
|
// maxFormatPasses bounds the format cascade for the same reason
|
|
@@ -36,12 +37,16 @@ func RunFormat(args []string) int {
|
|
|
36
37
|
// runFormat is the internal implementation of RunFormat. It drives the
|
|
37
38
|
// cascade loop and applies format-rule edits until convergence.
|
|
38
39
|
func runFormat(opts *subcommandOpts) int {
|
|
39
|
-
rules, err :=
|
|
40
|
+
rules, err := loadFormatRules(opts.pluginsJSON, opts.cwd, opts.tsconfig)
|
|
41
|
+
if err != nil {
|
|
42
|
+
fmt.Fprintln(os.Stderr, err)
|
|
43
|
+
return 2
|
|
44
|
+
}
|
|
45
|
+
resolver, err := newFormatCommandResolver(rules, opts.cwd, "")
|
|
40
46
|
if err != nil {
|
|
41
47
|
fmt.Fprintln(os.Stderr, err)
|
|
42
48
|
return 2
|
|
43
49
|
}
|
|
44
|
-
resolver := formatCommandResolver{inner: rules}
|
|
45
50
|
engine := NewEngineWithResolver(resolver)
|
|
46
51
|
engine.SetSerial(opts.singleThreaded)
|
|
47
52
|
needsRuleChecker := engine.NeedsTypeChecker()
|
|
@@ -100,6 +105,110 @@ func runFormat(opts *subcommandOpts) int {
|
|
|
100
105
|
// without requiring explicit rule declarations in the project config.
|
|
101
106
|
type formatCommandResolver struct {
|
|
102
107
|
inner RuleResolver
|
|
108
|
+
// ruleNames memoizes formatOptionRuleNames. The resolver's option set is
|
|
109
|
+
// immutable for the whole run, so the sorted format-rule slice is computed
|
|
110
|
+
// once and reused across ResolveRules (per file per pass), ActiveRuleNames,
|
|
111
|
+
// and EnabledRuleConfig. The field is a pointer so a nil zero value stays
|
|
112
|
+
// valid: callers that build formatCommandResolver{inner: ...} without it
|
|
113
|
+
// (tests, lsp) fall back to direct computation, and copying the struct by
|
|
114
|
+
// value (it is stored in the engine's RuleResolver interface and passed
|
|
115
|
+
// around as a value) shares the same underlying cache rather than copying a
|
|
116
|
+
// sync.Once lock.
|
|
117
|
+
ruleNames *formatRuleNamesCache
|
|
118
|
+
// entryDecisions memoizes the per-fileName ignored/matches decision that
|
|
119
|
+
// ResolveRules derives from fileIsIgnoredByEntry and fileMatchesAnyEntry.
|
|
120
|
+
// Both walk store.entries with glob matching and are recomputed identically
|
|
121
|
+
// for the same fileName on every format pass, so the result is cached once
|
|
122
|
+
// per file and reused. The field is a pointer (a *sync.Map) so the nil zero
|
|
123
|
+
// value stays valid for construction sites that omit it, and copying the
|
|
124
|
+
// resolver by value shares the same underlying map. sync.Map handles the
|
|
125
|
+
// engine's concurrent per-file ResolveRules calls without an extra lock.
|
|
126
|
+
entryDecisions *sync.Map
|
|
127
|
+
// defaultOptions holds the always-on format rules' options (from
|
|
128
|
+
// expandFormatBlock) to apply when the project config declares no `format`
|
|
129
|
+
// block — no block in lint.config.*, or no config file at all. nil when a
|
|
130
|
+
// format block is configured, so that block wins entirely. Keys are canonical
|
|
131
|
+
// format rule names; values are the marshaled options blob each rule decodes,
|
|
132
|
+
// exactly as a configured block would supply them.
|
|
133
|
+
defaultOptions RuleOptionsMap
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// newFormatCommandResolver wraps inner for the format command / LSP buffer path.
|
|
137
|
+
// When inner declares no `format` rules (no `format` block in lint.config.*, or
|
|
138
|
+
// no config file at all) it loads the documented default format rules, letting
|
|
139
|
+
// the nearest .vscode/settings.json under startDir override the indentation/eol
|
|
140
|
+
// keys. language scopes the settings.json language section ("" skips sections,
|
|
141
|
+
// e.g. the project-wide CLI path). A configured `format` block leaves
|
|
142
|
+
// defaultOptions nil so the block stays authoritative.
|
|
143
|
+
func newFormatCommandResolver(inner RuleResolver, startDir string, language string) (formatCommandResolver, error) {
|
|
144
|
+
r := formatCommandResolver{
|
|
145
|
+
inner: inner,
|
|
146
|
+
ruleNames: &formatRuleNamesCache{},
|
|
147
|
+
entryDecisions: &sync.Map{},
|
|
148
|
+
}
|
|
149
|
+
if !hasInnerFormatRules(inner) {
|
|
150
|
+
opts, err := defaultFormatOptions(editorFormatOverrides(startDir, language))
|
|
151
|
+
if err != nil {
|
|
152
|
+
return formatCommandResolver{}, err
|
|
153
|
+
}
|
|
154
|
+
r.defaultOptions = opts
|
|
155
|
+
}
|
|
156
|
+
return r, nil
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// hasInnerFormatRules reports whether inner already carries format-rule options,
|
|
160
|
+
// i.e. the project configured a `format` block (format/* options only ever come
|
|
161
|
+
// from a block). When true the block is authoritative and defaults are skipped.
|
|
162
|
+
func hasInnerFormatRules(inner RuleResolver) bool {
|
|
163
|
+
for name := range resolverOptions(inner) {
|
|
164
|
+
if isRegisteredFormatRule(name) {
|
|
165
|
+
return true
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return false
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// defaultFormatOptions expands the always-on default format ruleset (optionally
|
|
172
|
+
// overridden by editor settings) into the per-rule options map the resolver
|
|
173
|
+
// returns through RuleOptions. It reuses expandFormatBlock so the defaults are
|
|
174
|
+
// produced by exactly the same code path as a user-authored format block.
|
|
175
|
+
func defaultFormatOptions(overrides map[string]any) (RuleOptionsMap, error) {
|
|
176
|
+
expanded, err := expandFormatBlock(overrides)
|
|
177
|
+
if err != nil {
|
|
178
|
+
return nil, err
|
|
179
|
+
}
|
|
180
|
+
out := make(RuleOptionsMap, len(expanded))
|
|
181
|
+
for name, entry := range expanded {
|
|
182
|
+
tuple, ok := entry.([]any)
|
|
183
|
+
if !ok || len(tuple) < 2 {
|
|
184
|
+
continue
|
|
185
|
+
}
|
|
186
|
+
options, ok := tuple[1].(map[string]any)
|
|
187
|
+
if !ok {
|
|
188
|
+
continue
|
|
189
|
+
}
|
|
190
|
+
raw, err := json.Marshal(options)
|
|
191
|
+
if err != nil {
|
|
192
|
+
return nil, err
|
|
193
|
+
}
|
|
194
|
+
out[name] = raw
|
|
195
|
+
}
|
|
196
|
+
return out, nil
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// formatRuleNamesCache lazily computes and stores the sorted format-rule name
|
|
200
|
+
// slice for a resolver. sync.Once guarantees a single computation even when
|
|
201
|
+
// ResolveRules runs concurrently across files in the engine's parallel walk.
|
|
202
|
+
type formatRuleNamesCache struct {
|
|
203
|
+
once sync.Once
|
|
204
|
+
names []string
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// formatEntryDecision is the cached per-fileName outcome of the two entry-scope
|
|
208
|
+
// checks ResolveRules runs before applying its format-rule upgrade.
|
|
209
|
+
type formatEntryDecision struct {
|
|
210
|
+
ignoredByEntry bool
|
|
211
|
+
matchesEntry bool
|
|
103
212
|
}
|
|
104
213
|
|
|
105
214
|
// ResolveRules implements RuleResolver. It delegates to the inner resolver
|
|
@@ -127,10 +236,11 @@ func (r formatCommandResolver) ResolveRules(fileName string) ResolvedRuleConfig
|
|
|
127
236
|
if resolved.Ignored {
|
|
128
237
|
return resolved
|
|
129
238
|
}
|
|
130
|
-
|
|
239
|
+
decision := r.entryDecision(fileName)
|
|
240
|
+
if decision.ignoredByEntry {
|
|
131
241
|
return resolved
|
|
132
242
|
}
|
|
133
|
-
if !
|
|
243
|
+
if !decision.matchesEntry {
|
|
134
244
|
return resolved
|
|
135
245
|
}
|
|
136
246
|
if resolved.Rules == nil {
|
|
@@ -144,6 +254,36 @@ func (r formatCommandResolver) ResolveRules(fileName string) ResolvedRuleConfig
|
|
|
144
254
|
return resolved
|
|
145
255
|
}
|
|
146
256
|
|
|
257
|
+
// entryDecision returns the per-fileName ignored/matches outcome that
|
|
258
|
+
// ResolveRules needs, computing it from fileIsIgnoredByEntry and
|
|
259
|
+
// fileMatchesAnyEntry on first use and reusing it on later passes. When the
|
|
260
|
+
// memo is absent (a resolver built without entryDecisions, e.g. in tests or
|
|
261
|
+
// lsp) it computes the decision directly so behavior is identical, just
|
|
262
|
+
// uncached.
|
|
263
|
+
func (r formatCommandResolver) entryDecision(fileName string) formatEntryDecision {
|
|
264
|
+
if r.entryDecisions == nil {
|
|
265
|
+
return r.computeEntryDecision(fileName)
|
|
266
|
+
}
|
|
267
|
+
if cached, ok := r.entryDecisions.Load(fileName); ok {
|
|
268
|
+
return cached.(formatEntryDecision)
|
|
269
|
+
}
|
|
270
|
+
decision := r.computeEntryDecision(fileName)
|
|
271
|
+
// LoadOrStore keeps the first writer's value so concurrent passes over the
|
|
272
|
+
// same file agree; the decision is a pure function of fileName, so either
|
|
273
|
+
// value is correct.
|
|
274
|
+
actual, _ := r.entryDecisions.LoadOrStore(fileName, decision)
|
|
275
|
+
return actual.(formatEntryDecision)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// computeEntryDecision runs the two uncached entry-scope checks behind
|
|
279
|
+
// entryDecision.
|
|
280
|
+
func (r formatCommandResolver) computeEntryDecision(fileName string) formatEntryDecision {
|
|
281
|
+
return formatEntryDecision{
|
|
282
|
+
ignoredByEntry: r.fileIsIgnoredByEntry(fileName),
|
|
283
|
+
matchesEntry: r.fileMatchesAnyEntry(fileName),
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
147
287
|
// fileIsIgnoredByEntry reports whether any non-IgnoreOnly entry in the inner
|
|
148
288
|
// ConfigStore has an `ignores` glob that matches `fileName`. IgnoreOnly
|
|
149
289
|
// entries are already handled by ResolvedRuleConfig.Ignored — they are
|
|
@@ -154,19 +294,34 @@ func (r formatCommandResolver) ResolveRules(fileName string) ResolvedRuleConfig
|
|
|
154
294
|
// independently because its job is to upgrade format rules to `warn`, not to
|
|
155
295
|
// read the engine's resolved severity map.
|
|
156
296
|
func (r formatCommandResolver) fileIsIgnoredByEntry(fileName string) bool {
|
|
297
|
+
matched, _ := r.anyNonIgnoreEntry(func(entry *ConfigEntry) bool {
|
|
298
|
+
return entry.matchesIgnores(fileName)
|
|
299
|
+
})
|
|
300
|
+
return matched
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// anyNonIgnoreEntry resolves the inner resolver to its concrete *ConfigStore
|
|
304
|
+
// once and reports whether any non-IgnoreOnly entry satisfies `pred`. The
|
|
305
|
+
// second return value is false when the inner resolver is not a *ConfigStore
|
|
306
|
+
// (or is a nil one), letting each caller pick its own non-store default. This
|
|
307
|
+
// collapses the shared store-resolution and entry-walk that fileIsIgnoredByEntry
|
|
308
|
+
// and fileMatchesAnyEntry would otherwise duplicate; the per-caller default and
|
|
309
|
+
// the empty-entries base case stay in the callers where they differ.
|
|
310
|
+
func (r formatCommandResolver) anyNonIgnoreEntry(pred func(*ConfigEntry) bool) (matched bool, hasStore bool) {
|
|
157
311
|
store, ok := r.inner.(*ConfigStore)
|
|
158
312
|
if !ok || store == nil {
|
|
159
|
-
return false
|
|
313
|
+
return false, false
|
|
160
314
|
}
|
|
161
|
-
for
|
|
315
|
+
for i := range store.entries {
|
|
316
|
+
entry := &store.entries[i]
|
|
162
317
|
if entry.IgnoreOnly {
|
|
163
318
|
continue
|
|
164
319
|
}
|
|
165
|
-
if entry
|
|
166
|
-
return true
|
|
320
|
+
if pred(entry) {
|
|
321
|
+
return true, true
|
|
167
322
|
}
|
|
168
323
|
}
|
|
169
|
-
return false
|
|
324
|
+
return false, true
|
|
170
325
|
}
|
|
171
326
|
|
|
172
327
|
// fileMatchesAnyEntry reports whether `fileName` falls inside the `files`
|
|
@@ -193,22 +348,32 @@ func (r formatCommandResolver) fileIsIgnoredByEntry(fileName string) bool {
|
|
|
193
348
|
// rule-eligibility reasoned about from the outside, so the format
|
|
194
349
|
// upgrade applies the same as it does for an entry without `files`.
|
|
195
350
|
func (r formatCommandResolver) fileMatchesAnyEntry(fileName string) bool {
|
|
196
|
-
|
|
197
|
-
|
|
351
|
+
matched, hasStore := r.anyNonIgnoreEntry(func(entry *ConfigEntry) bool {
|
|
352
|
+
return entry.matchesFile(fileName)
|
|
353
|
+
})
|
|
354
|
+
if !hasStore {
|
|
355
|
+
// Not a *ConfigStore: conservative default, see the doc comment above.
|
|
198
356
|
return true
|
|
199
357
|
}
|
|
200
|
-
|
|
358
|
+
// A store with no entries (or only IgnoreOnly ones that the walk skipped)
|
|
359
|
+
// produces matched == false; an empty entries slice must still return true
|
|
360
|
+
// so format mode applies its default upgrade, matching ConfigStore behavior.
|
|
361
|
+
// The all-IgnoreOnly case is distinguished by a non-empty entries slice.
|
|
362
|
+
if !matched && len(r.storeEntries()) == 0 {
|
|
201
363
|
return true
|
|
202
364
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
365
|
+
return matched
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// storeEntries returns the inner resolver's config entries, or nil when the
|
|
369
|
+
// inner resolver is not a *ConfigStore. Used by fileMatchesAnyEntry to tell the
|
|
370
|
+
// empty-entries base case (return true) apart from the all-IgnoreOnly case
|
|
371
|
+
// (return false) after anyNonIgnoreEntry reports no match.
|
|
372
|
+
func (r formatCommandResolver) storeEntries() []ConfigEntry {
|
|
373
|
+
if store, ok := r.inner.(*ConfigStore); ok && store != nil {
|
|
374
|
+
return store.entries
|
|
210
375
|
}
|
|
211
|
-
return
|
|
376
|
+
return nil
|
|
212
377
|
}
|
|
213
378
|
|
|
214
379
|
// ActiveRuleNames implements RuleResolver. Returns the union of the inner
|
|
@@ -239,25 +404,57 @@ func (r formatCommandResolver) EnabledRuleConfig() RuleConfig {
|
|
|
239
404
|
return enabled
|
|
240
405
|
}
|
|
241
406
|
|
|
242
|
-
// RuleOptions implements RuleResolver
|
|
407
|
+
// RuleOptions implements RuleResolver. It prefers the inner resolver's options
|
|
408
|
+
// and falls back to the default format options for rules the project did not
|
|
409
|
+
// configure, so the default always-on rules receive their (possibly
|
|
410
|
+
// settings.json-overridden) options.
|
|
243
411
|
func (r formatCommandResolver) RuleOptions(name string) json.RawMessage {
|
|
244
|
-
|
|
412
|
+
if raw := r.inner.RuleOptions(name); len(raw) > 0 {
|
|
413
|
+
return raw
|
|
414
|
+
}
|
|
415
|
+
if r.defaultOptions != nil {
|
|
416
|
+
if raw, ok := r.defaultOptions[name]; ok {
|
|
417
|
+
return raw
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return nil
|
|
245
421
|
}
|
|
246
422
|
|
|
247
423
|
// formatOptionRuleNames returns the sorted list of rule names from the inner
|
|
248
424
|
// resolver's options that are registered as format rules. These are the rules
|
|
249
425
|
// that formatCommandResolver promotes from off to warn.
|
|
250
426
|
func (r formatCommandResolver) formatOptionRuleNames() []string {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
427
|
+
if r.ruleNames == nil {
|
|
428
|
+
// Nil cache (a resolver built without the memo, e.g. in tests or lsp):
|
|
429
|
+
// compute directly. Construction sites that want the memo set ruleNames.
|
|
430
|
+
return r.computeFormatOptionRuleNames()
|
|
254
431
|
}
|
|
432
|
+
r.ruleNames.once.Do(func() {
|
|
433
|
+
r.ruleNames.names = r.computeFormatOptionRuleNames()
|
|
434
|
+
})
|
|
435
|
+
return r.ruleNames.names
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// computeFormatOptionRuleNames does the uncached work behind
|
|
439
|
+
// formatOptionRuleNames: collect the inner resolver's option names that are
|
|
440
|
+
// registered format rules, sorted. The result order is stable (sorted) so
|
|
441
|
+
// memoizing it does not change any caller's observed ordering.
|
|
442
|
+
func (r formatCommandResolver) computeFormatOptionRuleNames() []string {
|
|
443
|
+
options := resolverOptions(r.inner)
|
|
255
444
|
names := make([]string, 0, len(options))
|
|
256
445
|
for name := range options {
|
|
257
446
|
if isRegisteredFormatRule(name) {
|
|
258
447
|
names = append(names, name)
|
|
259
448
|
}
|
|
260
449
|
}
|
|
450
|
+
if len(names) == 0 {
|
|
451
|
+
// No `format` block configured: fall back to the default always-on set so
|
|
452
|
+
// the formatter still runs, with documented defaults plus any
|
|
453
|
+
// .vscode/settings.json overrides folded into defaultOptions.
|
|
454
|
+
for name := range r.defaultOptions {
|
|
455
|
+
names = append(names, name)
|
|
456
|
+
}
|
|
457
|
+
}
|
|
261
458
|
sort.Strings(names)
|
|
262
459
|
return names
|
|
263
460
|
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
package linthost
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"errors"
|
|
6
|
+
"os"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"strings"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// errNoLintConfigFile is the sentinel LoadConfigResolver wraps when no
|
|
12
|
+
// lint.config.*/ttsc-lint.config.* file is found. The format paths
|
|
13
|
+
// (loadFormatRules) treat it as "use defaults" rather than a hard error, so
|
|
14
|
+
// formatOnSave / `ttsc format` work even when a project ships no lint config.
|
|
15
|
+
var errNoLintConfigFile = errors.New("@ttsc/lint: no lint.config.* or ttsc-lint.config.* file found")
|
|
16
|
+
|
|
17
|
+
// loadFormatRules loads the rule resolver for the format paths, tolerating a
|
|
18
|
+
// missing lint config. The lint check/build path requires a config and surfaces
|
|
19
|
+
// the error, but formatting falls back to the documented defaults: a project
|
|
20
|
+
// with no lint.config.* still formats on save. Any other load error (a broken
|
|
21
|
+
// or malformed config) is propagated unchanged.
|
|
22
|
+
func loadFormatRules(pluginsJSON, cwd, tsconfigPath string) (RuleResolver, error) {
|
|
23
|
+
rules, err := loadRules(pluginsJSON, cwd, tsconfigPath)
|
|
24
|
+
if err != nil {
|
|
25
|
+
if errors.Is(err, errNoLintConfigFile) {
|
|
26
|
+
return RuleConfig{}, nil
|
|
27
|
+
}
|
|
28
|
+
return nil, err
|
|
29
|
+
}
|
|
30
|
+
return rules, nil
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// editorFormatOverrides reads the nearest ancestor `.vscode/settings.json`
|
|
34
|
+
// relative to startDir and maps the editor's formatting settings onto the
|
|
35
|
+
// `format` block keys the default formatter understands. Only keys the user
|
|
36
|
+
// explicitly set are returned, so any setting that is absent falls through to
|
|
37
|
+
// the documented format defaults.
|
|
38
|
+
//
|
|
39
|
+
// This is consulted only when lint.config.* configures no `format` block: a
|
|
40
|
+
// configured format block always wins and never reaches this path. Within
|
|
41
|
+
// settings.json a language-specific section matching `language` (e.g.
|
|
42
|
+
// "[typescript]" or a combined "[javascript][typescript][json]") wins over the
|
|
43
|
+
// top-level keys, mirroring VS Code's own resolution. Pass language="" to skip
|
|
44
|
+
// language sections (e.g. the project-wide `ttsc format` CLI path).
|
|
45
|
+
//
|
|
46
|
+
// Mapping (values use JSON types so they parse identically to a real `format`
|
|
47
|
+
// block, where numbers decode as float64):
|
|
48
|
+
//
|
|
49
|
+
// - editor.tabSize -> tabWidth (number)
|
|
50
|
+
// - editor.insertSpaces -> useTabs (bool, inverted)
|
|
51
|
+
// - files.eol -> endOfLine ("\n" -> "lf", "\r\n" -> "crlf")
|
|
52
|
+
//
|
|
53
|
+
// editor.detectIndentation auto-detection is intentionally not emulated; the
|
|
54
|
+
// explicit tabSize/insertSpaces are honored as written. A relative startDir is
|
|
55
|
+
// ignored (returns no overrides) so unit tests that format from a bare cwd stay
|
|
56
|
+
// hermetic and never walk up into a real workspace's .vscode directory.
|
|
57
|
+
func editorFormatOverrides(startDir string, language string) map[string]any {
|
|
58
|
+
out := map[string]any{}
|
|
59
|
+
if !filepath.IsAbs(startDir) {
|
|
60
|
+
return out
|
|
61
|
+
}
|
|
62
|
+
settings, ok := loadNearestVSCodeSettings(startDir)
|
|
63
|
+
if !ok {
|
|
64
|
+
return out
|
|
65
|
+
}
|
|
66
|
+
// Top-level keys first, then language-specific sections override them.
|
|
67
|
+
applyEditorSettings(out, settings)
|
|
68
|
+
if language != "" {
|
|
69
|
+
for key, value := range settings {
|
|
70
|
+
if !sectionMatchesLanguage(key, language) {
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
if section, ok := value.(map[string]any); ok {
|
|
74
|
+
applyEditorSettings(out, section)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// applyEditorSettings reads the recognized editor keys from a settings object
|
|
82
|
+
// (either the top-level document or a language-specific section) and writes the
|
|
83
|
+
// corresponding format-block overrides into out, overwriting earlier values so
|
|
84
|
+
// callers can layer language sections over the top-level defaults.
|
|
85
|
+
func applyEditorSettings(out map[string]any, settings map[string]any) {
|
|
86
|
+
if v, ok := settings["editor.tabSize"]; ok {
|
|
87
|
+
if f, ok := v.(float64); ok {
|
|
88
|
+
out["tabWidth"] = f
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if v, ok := settings["editor.insertSpaces"]; ok {
|
|
92
|
+
if b, ok := v.(bool); ok {
|
|
93
|
+
out["useTabs"] = !b
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if v, ok := settings["files.eol"]; ok {
|
|
97
|
+
if s, ok := v.(string); ok {
|
|
98
|
+
switch s {
|
|
99
|
+
case "\n":
|
|
100
|
+
out["endOfLine"] = "lf"
|
|
101
|
+
case "\r\n":
|
|
102
|
+
out["endOfLine"] = "crlf"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// sectionMatchesLanguage reports whether a settings.json key is a
|
|
109
|
+
// language-specific section header (e.g. "[typescript]" or the combined
|
|
110
|
+
// "[javascript][typescript][json]") that applies to language. An empty
|
|
111
|
+
// language never matches.
|
|
112
|
+
func sectionMatchesLanguage(key string, language string) bool {
|
|
113
|
+
if language == "" {
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
if !strings.HasPrefix(key, "[") || !strings.HasSuffix(key, "]") {
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
inner := strings.TrimSuffix(strings.TrimPrefix(key, "["), "]")
|
|
120
|
+
for _, lang := range strings.Split(inner, "][") {
|
|
121
|
+
if lang == language {
|
|
122
|
+
return true
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// vscodeLanguageID maps a file extension to the VS Code language identifier
|
|
129
|
+
// used in settings.json language sections. Extensions outside the formatter's
|
|
130
|
+
// supported set return "" so no language section matches.
|
|
131
|
+
func vscodeLanguageID(filePath string) string {
|
|
132
|
+
switch strings.ToLower(filepath.Ext(filePath)) {
|
|
133
|
+
case ".ts", ".mts", ".cts":
|
|
134
|
+
return "typescript"
|
|
135
|
+
case ".tsx":
|
|
136
|
+
return "typescriptreact"
|
|
137
|
+
case ".js", ".mjs", ".cjs":
|
|
138
|
+
return "javascript"
|
|
139
|
+
case ".jsx":
|
|
140
|
+
return "javascriptreact"
|
|
141
|
+
default:
|
|
142
|
+
return ""
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// loadNearestVSCodeSettings walks up from startDir looking for a
|
|
147
|
+
// `.vscode/settings.json`, returning the first one found decoded as a JSONC
|
|
148
|
+
// document. It returns ok=false when none exists or the file cannot be parsed,
|
|
149
|
+
// so a broken settings file degrades to the format defaults instead of breaking
|
|
150
|
+
// the formatter.
|
|
151
|
+
func loadNearestVSCodeSettings(startDir string) (map[string]any, bool) {
|
|
152
|
+
dir := startDir
|
|
153
|
+
for {
|
|
154
|
+
candidate := filepath.Join(dir, ".vscode", "settings.json")
|
|
155
|
+
if data, err := os.ReadFile(candidate); err == nil {
|
|
156
|
+
var parsed map[string]any
|
|
157
|
+
if json.Unmarshal(stripJSONC(data), &parsed) == nil {
|
|
158
|
+
return parsed, true
|
|
159
|
+
}
|
|
160
|
+
return nil, false
|
|
161
|
+
}
|
|
162
|
+
parent := filepath.Dir(dir)
|
|
163
|
+
if parent == dir {
|
|
164
|
+
return nil, false
|
|
165
|
+
}
|
|
166
|
+
dir = parent
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// stripJSONC removes `//` line comments, `/* */` block comments, and trailing
|
|
171
|
+
// commas from a JSONC byte slice so encoding/json can parse VS Code's
|
|
172
|
+
// settings.json. It is string-literal aware: comment markers and commas inside
|
|
173
|
+
// string values are preserved.
|
|
174
|
+
func stripJSONC(data []byte) []byte {
|
|
175
|
+
out := make([]byte, 0, len(data))
|
|
176
|
+
inString := false
|
|
177
|
+
escaped := false
|
|
178
|
+
for i := 0; i < len(data); i++ {
|
|
179
|
+
c := data[i]
|
|
180
|
+
if inString {
|
|
181
|
+
out = append(out, c)
|
|
182
|
+
if escaped {
|
|
183
|
+
escaped = false
|
|
184
|
+
continue
|
|
185
|
+
}
|
|
186
|
+
switch c {
|
|
187
|
+
case '\\':
|
|
188
|
+
escaped = true
|
|
189
|
+
case '"':
|
|
190
|
+
inString = false
|
|
191
|
+
}
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
switch {
|
|
195
|
+
case c == '"':
|
|
196
|
+
inString = true
|
|
197
|
+
out = append(out, c)
|
|
198
|
+
case c == '/' && i+1 < len(data) && data[i+1] == '/':
|
|
199
|
+
for i < len(data) && data[i] != '\n' {
|
|
200
|
+
i++
|
|
201
|
+
}
|
|
202
|
+
if i < len(data) {
|
|
203
|
+
out = append(out, data[i])
|
|
204
|
+
}
|
|
205
|
+
case c == '/' && i+1 < len(data) && data[i+1] == '*':
|
|
206
|
+
i += 2
|
|
207
|
+
for i+1 < len(data) && !(data[i] == '*' && data[i+1] == '/') {
|
|
208
|
+
i++
|
|
209
|
+
}
|
|
210
|
+
i++
|
|
211
|
+
default:
|
|
212
|
+
out = append(out, c)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return dropTrailingCommas(out)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// dropTrailingCommas removes a comma that is followed (after optional
|
|
219
|
+
// whitespace) by a closing `}` or `]`, the one JSONC affordance encoding/json
|
|
220
|
+
// rejects. It is string-literal aware so commas inside strings survive.
|
|
221
|
+
func dropTrailingCommas(data []byte) []byte {
|
|
222
|
+
out := make([]byte, 0, len(data))
|
|
223
|
+
inString := false
|
|
224
|
+
escaped := false
|
|
225
|
+
for i := 0; i < len(data); i++ {
|
|
226
|
+
c := data[i]
|
|
227
|
+
if inString {
|
|
228
|
+
out = append(out, c)
|
|
229
|
+
if escaped {
|
|
230
|
+
escaped = false
|
|
231
|
+
continue
|
|
232
|
+
}
|
|
233
|
+
switch c {
|
|
234
|
+
case '\\':
|
|
235
|
+
escaped = true
|
|
236
|
+
case '"':
|
|
237
|
+
inString = false
|
|
238
|
+
}
|
|
239
|
+
continue
|
|
240
|
+
}
|
|
241
|
+
if c == '"' {
|
|
242
|
+
inString = true
|
|
243
|
+
out = append(out, c)
|
|
244
|
+
continue
|
|
245
|
+
}
|
|
246
|
+
if c == ',' {
|
|
247
|
+
j := i + 1
|
|
248
|
+
for j < len(data) && isJSONSpace(data[j]) {
|
|
249
|
+
j++
|
|
250
|
+
}
|
|
251
|
+
if j < len(data) && (data[j] == '}' || data[j] == ']') {
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
out = append(out, c)
|
|
256
|
+
}
|
|
257
|
+
return out
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
func isJSONSpace(c byte) bool {
|
|
261
|
+
return c == ' ' || c == '\t' || c == '\n' || c == '\r'
|
|
262
|
+
}
|
package/linthost/lsp.go
CHANGED
|
@@ -302,7 +302,7 @@ func findingToLSPDiagnostic(finding *Finding) lspDiagnostic {
|
|
|
302
302
|
Range: lspRangeForFinding(finding),
|
|
303
303
|
Severity: lspSeverity(finding.Severity),
|
|
304
304
|
Code: finding.Rule,
|
|
305
|
-
Source: "ttsc/lint",
|
|
305
|
+
Source: "@ttsc/lint",
|
|
306
306
|
Message: finding.Message,
|
|
307
307
|
}
|
|
308
308
|
}
|
|
@@ -479,13 +479,17 @@ func lspFormatBuffer(content string, opts *lspCommandOptions) (*lspWorkspaceEdit
|
|
|
479
479
|
return nil, 0
|
|
480
480
|
}
|
|
481
481
|
|
|
482
|
-
rules, err :=
|
|
482
|
+
rules, err := loadFormatRules(opts.pluginsJSON, opts.cwd, opts.tsconfig)
|
|
483
483
|
if err != nil {
|
|
484
484
|
fmt.Fprintln(os.Stderr, err)
|
|
485
485
|
return nil, 2
|
|
486
486
|
}
|
|
487
|
-
|
|
488
|
-
|
|
487
|
+
resolver, err := newFormatCommandResolver(rules, filepath.Dir(target), vscodeLanguageID(target))
|
|
488
|
+
if err != nil {
|
|
489
|
+
fmt.Fprintln(os.Stderr, err)
|
|
490
|
+
return nil, 2
|
|
491
|
+
}
|
|
492
|
+
engine := NewEngineWithResolver(resolver)
|
|
489
493
|
if engine.NeedsTypeChecker() {
|
|
490
494
|
// A format-class contributor rule (formatContributorAdapter) needs the type
|
|
491
495
|
// checker, which the single-file in-memory parse can't supply. Fall back to
|
|
@@ -118,6 +118,9 @@ func dispatchNode(ctx *PrintContext, node *shimast.Node) (Doc, bool, bool) {
|
|
|
118
118
|
case shimast.KindReturnStatement:
|
|
119
119
|
doc, covered := printReturnStatement(ctx, node)
|
|
120
120
|
return doc, covered, true
|
|
121
|
+
case shimast.KindConditionalExpression:
|
|
122
|
+
doc, covered := printConditionalExpression(ctx, node)
|
|
123
|
+
return doc, covered, true
|
|
121
124
|
}
|
|
122
125
|
return Doc{}, false, false
|
|
123
126
|
}
|
package/linthost/print_doc.go
CHANGED
|
@@ -79,6 +79,7 @@ const (
|
|
|
79
79
|
docConcat
|
|
80
80
|
docLineSuffix
|
|
81
81
|
docConditionalGroup
|
|
82
|
+
docFill
|
|
82
83
|
)
|
|
83
84
|
|
|
84
85
|
// Doc is one node in the layout tree. Only the fields relevant to the
|
|
@@ -135,6 +136,15 @@ func ConditionalGroup(options ...Doc) Doc {
|
|
|
135
136
|
return Doc{Kind: docConditionalGroup, Children: options}
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
// Fill renders an alternating [content, separator, content, separator, …,
|
|
140
|
+
// content] sequence with Wadler/Prettier "fill" semantics: it places as many
|
|
141
|
+
// contents on a line as fit, breaking a separator to a new line only when the
|
|
142
|
+
// next content (with its separator) would overflow. Used for concisely-printed
|
|
143
|
+
// numeric arrays (`[1, 2, 3, … ]` packed several per line), where a plain
|
|
144
|
+
// one-item-per-line break would waste space. `parts` must have odd length
|
|
145
|
+
// (content at even indices, separators at odd).
|
|
146
|
+
func Fill(parts ...Doc) Doc { return Doc{Kind: docFill, Children: parts} }
|
|
147
|
+
|
|
138
148
|
// Indent adds `width` columns of indentation to every newline emitted by
|
|
139
149
|
// the child doc. Nesting composes.
|
|
140
150
|
func Indent(width int, parts ...Doc) Doc {
|
|
@@ -162,6 +172,9 @@ func Concat(parts ...Doc) Doc {
|
|
|
162
172
|
|
|
163
173
|
// LineSuffix queues output until the next line break. Used for trailing
|
|
164
174
|
// line comments that must appear after the current source line ends.
|
|
175
|
+
//
|
|
176
|
+
// The payload is expected to be single-line: it is emitted verbatim at the
|
|
177
|
+
// line break and is NOT re-indented across any embedded newlines.
|
|
165
178
|
func LineSuffix(parts ...Doc) Doc {
|
|
166
179
|
return Doc{Kind: docLineSuffix, Children: parts}
|
|
167
180
|
}
|