@ttsc/lint 0.18.4 → 0.19.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 +125 -43
- package/go.mod +5 -4
- package/internal/rulecode/rulecode.go +88 -0
- package/lib/index.d.ts +10 -1
- package/lib/index.js +45 -17
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +3 -0
- package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
- package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
- package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
- package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
- package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
- package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
- package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
- package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
- package/lib/structures/rules/index.d.ts +3 -0
- package/lib/structures/rules/index.js +3 -0
- package/lib/structures/rules/index.js.map +1 -1
- package/linthost/ast_helpers.go +521 -23
- package/linthost/ast_selector_matcher.go +1628 -0
- package/linthost/ast_selector_parser.go +628 -0
- package/linthost/comment_scan.go +154 -0
- package/linthost/compile.go +129 -79
- package/linthost/config.go +352 -88
- package/linthost/config_format.go +15 -1
- package/linthost/contrib_adapter.go +47 -8
- package/linthost/declaration_rules.go +12 -11
- package/linthost/directives.go +9 -67
- package/linthost/dispatch.go +37 -3
- package/linthost/engine.go +368 -53
- package/linthost/escape_string.go +77 -0
- package/linthost/fix.go +133 -23
- package/linthost/flags_gen.go +18 -17
- package/linthost/format.go +73 -167
- package/linthost/format_editor_settings.go +134 -26
- package/linthost/grapheme_tables_gen.go +1920 -0
- package/linthost/host.go +159 -61
- package/linthost/literal_escape_scan.go +117 -0
- package/linthost/lsp.go +545 -98
- package/linthost/numeric_literal_precision.go +228 -0
- package/linthost/polyfill_browserslist.go +1439 -0
- package/linthost/polyfill_browserslist_config.go +603 -0
- package/linthost/polyfill_corejs.go +750 -0
- package/linthost/polyfill_data.go +113 -0
- package/linthost/polyfill_data_gen.json +39868 -0
- package/linthost/print_nodes_array.go +7 -1
- package/linthost/print_nodes_object.go +7 -1
- package/linthost/project_engine.go +231 -0
- package/linthost/project_rules.go +78 -0
- package/linthost/regex_clean.go +191 -0
- package/linthost/regex_tree.go +1544 -0
- package/linthost/regex_tree_optimizer.go +1685 -0
- package/linthost/regexp_cache.go +44 -0
- package/linthost/rule_codes.go +105 -0
- package/linthost/rule_codes.json +745 -0
- package/linthost/rules_ban_ts_comment.go +312 -0
- package/linthost/rules_boundaries.go +128 -43
- package/linthost/rules_boundaries_dependencies.go +1013 -0
- package/linthost/rules_core_extra.go +62 -92
- package/linthost/rules_cypress.go +1 -1
- package/linthost/rules_default_case.go +87 -11
- package/linthost/rules_dupes.go +38 -13
- package/linthost/rules_empty.go +188 -20
- package/linthost/rules_escape.go +62 -48
- package/linthost/rules_format_arrow_parens.go +19 -5
- package/linthost/rules_format_bracket_spacing.go +1 -1
- package/linthost/rules_format_clause_join.go +1 -1
- package/linthost/rules_format_declaration_header.go +31 -19
- package/linthost/rules_format_indent.go +1 -1
- package/linthost/rules_format_jsdoc.go +1 -1
- package/linthost/rules_format_orphan_semi.go +1 -1
- package/linthost/rules_format_parameter_properties.go +17 -7
- package/linthost/rules_format_print_width.go +1 -1
- package/linthost/rules_format_quote_props.go +1 -1
- package/linthost/rules_format_quotes.go +1 -1
- package/linthost/rules_format_semi.go +106 -79
- package/linthost/rules_format_sort_imports.go +79 -45
- package/linthost/rules_format_statement_split.go +1 -1
- package/linthost/rules_format_ternary_nullish_parens.go +1 -1
- package/linthost/rules_format_trailing_comma.go +64 -1
- package/linthost/rules_format_whitespace.go +1 -1
- package/linthost/rules_functional.go +21 -22
- package/linthost/rules_gap.go +80 -3
- package/linthost/rules_grouped_accessor_pairs.go +84 -25
- package/linthost/rules_guard_for_in.go +64 -147
- package/linthost/rules_jsx_a11y.go +232 -22
- package/linthost/rules_logic.go +84 -38
- package/linthost/rules_nextjs.go +105 -16
- package/linthost/rules_no_class_assign.go +70 -0
- package/linthost/rules_no_duplicate_imports.go +266 -0
- package/linthost/rules_no_else_return.go +156 -38
- package/linthost/rules_no_extend_native.go +113 -17
- package/linthost/rules_no_fallthrough.go +1013 -0
- package/linthost/rules_no_func_assign.go +70 -0
- package/linthost/rules_no_import_assign.go +278 -86
- package/linthost/rules_no_inner_declarations.go +186 -0
- package/linthost/rules_no_loop_func.go +438 -53
- package/linthost/rules_no_mixed_operators.go +208 -52
- package/linthost/rules_no_param_reassign.go +376 -61
- package/linthost/rules_no_promise_executor_return.go +156 -0
- package/linthost/rules_no_restricted_imports.go +1013 -57
- package/linthost/rules_no_restricted_syntax.go +190 -26
- package/linthost/rules_no_useless_constructor.go +6 -36
- package/linthost/rules_problems.go +154 -311
- package/linthost/rules_promise.go +2578 -109
- package/linthost/rules_react_extras.go +71 -45
- package/linthost/rules_react_hooks.go +18 -2
- package/linthost/rules_react_perf.go +1 -0
- package/linthost/rules_react_refresh.go +1 -1
- package/linthost/rules_regexp.go +230 -14
- package/linthost/rules_security.go +45 -19
- package/linthost/rules_self.go +21 -1
- package/linthost/rules_solid.go +13 -7
- package/linthost/rules_storybook.go +1 -1
- package/linthost/rules_suggestions.go +490 -84
- package/linthost/rules_testing_library.go +12 -1
- package/linthost/rules_ts.go +227 -54
- package/linthost/rules_ts_async.go +0 -131
- package/linthost/rules_ts_no_base_to_string.go +3 -4
- package/linthost/rules_ts_no_misused_promises.go +817 -0
- package/linthost/rules_ts_no_restricted_types.go +320 -34
- package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
- package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
- package/linthost/rules_ts_require_array_sort_compare.go +5 -5
- package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
- package/linthost/rules_unicorn_better_regex.go +224 -16
- package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
- package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
- package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
- package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
- package/linthost/rules_unicorn_escape_case.go +30 -19
- package/linthost/rules_unicorn_filename_case.go +763 -16
- package/linthost/rules_unicorn_import_style.go +783 -17
- package/linthost/rules_unicorn_isolated_functions.go +934 -14
- package/linthost/rules_unicorn_no_hex_escape.go +26 -11
- package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
- package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
- package/linthost/rules_unicorn_no_unused_properties.go +756 -19
- package/linthost/rules_unicorn_number_literal_case.go +58 -39
- package/linthost/rules_unicorn_prefer_at.go +7 -3
- package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
- package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
- package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
- package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
- package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
- package/linthost/rules_unicorn_string_content.go +611 -15
- package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
- package/linthost/rules_unicorn_template_indent.go +923 -18
- package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
- package/linthost/rules_unicorn_throw_new_error.go +218 -26
- package/linthost/rules_var.go +829 -142
- package/linthost/string_length.go +222 -0
- package/package.json +4 -3
- package/rule/project.go +206 -0
- package/rule/rule.go +58 -1
- package/src/index.ts +58 -17
- package/src/structures/ITtscLintConfig.ts +3 -0
- package/src/structures/TtscLintRuleSetting.ts +3 -2
- package/src/structures/format/ITtscLintFormat.ts +3 -2
- package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
- package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
- package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
- package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
- package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
- package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
- package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
- package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
- package/src/structures/rules/ITtscLintRules.ts +7 -2
- package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
- package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
- package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
- package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
- package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
- package/src/structures/rules/index.ts +3 -0
|
@@ -5,6 +5,7 @@ import (
|
|
|
5
5
|
"strings"
|
|
6
6
|
|
|
7
7
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
8
|
+
"golang.org/x/text/language"
|
|
8
9
|
)
|
|
9
10
|
|
|
10
11
|
type jsxAttr struct {
|
|
@@ -456,7 +457,7 @@ func jsxIsFocusable(info jsxElementInfo) bool {
|
|
|
456
457
|
|
|
457
458
|
func jsxIsNonInteractiveElement(tag string) bool {
|
|
458
459
|
switch tag {
|
|
459
|
-
case "article", "aside", "
|
|
460
|
+
case "article", "aside", "footer", "header", "li", "main", "nav", "ol", "p", "section", "ul",
|
|
460
461
|
"h1", "h2", "h3", "h4", "h5", "h6":
|
|
461
462
|
return true
|
|
462
463
|
}
|
|
@@ -573,7 +574,26 @@ var jsxAutocompleteTokens = map[string]bool{
|
|
|
573
574
|
"transaction-currency": true, "transaction-amount": true, "language": true, "bday": true,
|
|
574
575
|
"bday-day": true, "bday-month": true, "bday-year": true, "sex": true, "tel": true,
|
|
575
576
|
"tel-country-code": true, "tel-national": true, "tel-area-code": true, "tel-local": true,
|
|
576
|
-
"tel-
|
|
577
|
+
"tel-local-prefix": true, "tel-local-suffix": true, "tel-extension": true,
|
|
578
|
+
"impp": true, "url": true, "photo": true,
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
var jsxAutocompleteContactQualifiers = map[string]bool{
|
|
582
|
+
"home": true, "work": true, "mobile": true, "fax": true, "pager": true,
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
var jsxAutocompleteContactPurposes = map[string]bool{
|
|
586
|
+
"email": true, "impp": true, "tel": true, "tel-country-code": true,
|
|
587
|
+
"tel-national": true, "tel-area-code": true, "tel-local": true,
|
|
588
|
+
"tel-local-prefix": true, "tel-local-suffix": true, "tel-extension": true,
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
var jsxHTMLInputTypes = map[string]bool{
|
|
592
|
+
"button": true, "checkbox": true, "color": true, "date": true, "datetime-local": true,
|
|
593
|
+
"email": true, "file": true, "hidden": true, "image": true, "month": true,
|
|
594
|
+
"number": true, "password": true, "radio": true, "range": true, "reset": true,
|
|
595
|
+
"search": true, "submit": true, "tel": true, "text": true, "time": true,
|
|
596
|
+
"url": true, "week": true,
|
|
577
597
|
}
|
|
578
598
|
|
|
579
599
|
type jsxA11yAltText struct{}
|
|
@@ -608,8 +628,10 @@ func (jsxA11yAltText) Check(ctx *Context, node *shimast.Node) {
|
|
|
608
628
|
|
|
609
629
|
type jsxA11yAnchorHasContent struct{}
|
|
610
630
|
|
|
611
|
-
func (jsxA11yAnchorHasContent) Name() string
|
|
612
|
-
func (jsxA11yAnchorHasContent) Visits() []shimast.Kind {
|
|
631
|
+
func (jsxA11yAnchorHasContent) Name() string { return "jsx-a11y/anchor-has-content" }
|
|
632
|
+
func (jsxA11yAnchorHasContent) Visits() []shimast.Kind {
|
|
633
|
+
return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
|
|
634
|
+
}
|
|
613
635
|
func (jsxA11yAnchorHasContent) Check(ctx *Context, node *shimast.Node) {
|
|
614
636
|
info := jsxElementFromNode(node)
|
|
615
637
|
if info.tag == "a" && !info.spread && !jsxHasAccessibleLabel(info) {
|
|
@@ -756,21 +778,123 @@ func (jsxA11yAutocompleteValid) Visits() []shimast.Kind {
|
|
|
756
778
|
}
|
|
757
779
|
func (jsxA11yAutocompleteValid) Check(ctx *Context, node *shimast.Node) {
|
|
758
780
|
info := jsxElementFromNode(node)
|
|
759
|
-
|
|
781
|
+
if info.tag != "input" {
|
|
782
|
+
return
|
|
783
|
+
}
|
|
784
|
+
attr, ok := info.attrs["autoComplete"]
|
|
760
785
|
if !ok {
|
|
761
|
-
attr, ok =
|
|
786
|
+
attr, ok = info.attrs["autocomplete"]
|
|
762
787
|
}
|
|
763
|
-
if !ok
|
|
788
|
+
if !ok {
|
|
764
789
|
return
|
|
765
790
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
791
|
+
value, ok := jsxStringLiteralAttrValue(attr)
|
|
792
|
+
if !ok || strings.TrimSpace(value) == "" {
|
|
793
|
+
return
|
|
794
|
+
}
|
|
795
|
+
purpose, valid := jsxAutocompletePurpose(value)
|
|
796
|
+
if !valid {
|
|
797
|
+
ctx.Report(attr.node, "autocomplete contains an invalid token sequence.")
|
|
798
|
+
return
|
|
799
|
+
}
|
|
800
|
+
if !jsxAutocompletePurposeAllowsInputType(purpose, jsxAutocompleteInputType(info)) {
|
|
801
|
+
ctx.Report(attr.node, "autocomplete token is not valid for this input type.")
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
func jsxStringLiteralAttrValue(attr jsxAttr) (string, bool) {
|
|
806
|
+
if attr.node == nil || attr.boolean {
|
|
807
|
+
return "", false
|
|
808
|
+
}
|
|
809
|
+
jsx := attr.node.AsJsxAttribute()
|
|
810
|
+
if jsx == nil || jsx.Initializer == nil {
|
|
811
|
+
return "", false
|
|
812
|
+
}
|
|
813
|
+
switch jsx.Initializer.Kind {
|
|
814
|
+
case shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral:
|
|
815
|
+
return stringLiteralText(jsx.Initializer), true
|
|
816
|
+
case shimast.KindJsxExpression:
|
|
817
|
+
expression := jsx.Initializer.AsJsxExpression()
|
|
818
|
+
if expression != nil && expression.Expression != nil && (expression.Expression.Kind == shimast.KindStringLiteral || expression.Expression.Kind == shimast.KindNoSubstitutionTemplateLiteral) {
|
|
819
|
+
return stringLiteralText(expression.Expression), true
|
|
769
820
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
821
|
+
}
|
|
822
|
+
return "", false
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
func jsxAutocompletePurpose(value string) (string, bool) {
|
|
826
|
+
tokens := strings.Fields(strings.ToLower(value))
|
|
827
|
+
if len(tokens) == 1 && (tokens[0] == "on" || tokens[0] == "off") {
|
|
828
|
+
return tokens[0], true
|
|
829
|
+
}
|
|
830
|
+
index := 0
|
|
831
|
+
if index < len(tokens) && strings.HasPrefix(tokens[index], "section-") {
|
|
832
|
+
if len(tokens[index]) == len("section-") {
|
|
833
|
+
return "", false
|
|
834
|
+
}
|
|
835
|
+
index++
|
|
836
|
+
}
|
|
837
|
+
if index < len(tokens) && (tokens[index] == "shipping" || tokens[index] == "billing") {
|
|
838
|
+
index++
|
|
839
|
+
}
|
|
840
|
+
qualifier := ""
|
|
841
|
+
if index < len(tokens) && jsxAutocompleteContactQualifiers[tokens[index]] {
|
|
842
|
+
qualifier = tokens[index]
|
|
843
|
+
index++
|
|
844
|
+
}
|
|
845
|
+
if index >= len(tokens) {
|
|
846
|
+
return "", false
|
|
847
|
+
}
|
|
848
|
+
purpose := tokens[index]
|
|
849
|
+
index++
|
|
850
|
+
if index < len(tokens) {
|
|
851
|
+
if index != len(tokens)-1 || tokens[index] != "webauthn" {
|
|
852
|
+
return "", false
|
|
773
853
|
}
|
|
854
|
+
index++
|
|
855
|
+
}
|
|
856
|
+
if purpose == "on" || purpose == "off" || !jsxAutocompleteTokens[purpose] {
|
|
857
|
+
return "", false
|
|
858
|
+
}
|
|
859
|
+
if qualifier != "" && !jsxAutocompleteContactPurposes[purpose] {
|
|
860
|
+
return "", false
|
|
861
|
+
}
|
|
862
|
+
return purpose, index == len(tokens)
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
func jsxAutocompleteInputType(info jsxElementInfo) string {
|
|
866
|
+
attr, ok := jsxKnownAttr(info.attrs, "type")
|
|
867
|
+
if !ok {
|
|
868
|
+
return "text"
|
|
869
|
+
}
|
|
870
|
+
inputType := strings.ToLower(strings.TrimSpace(attr.value))
|
|
871
|
+
if !jsxHTMLInputTypes[inputType] {
|
|
872
|
+
return "text"
|
|
873
|
+
}
|
|
874
|
+
return inputType
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
func jsxAutocompletePurposeAllowsInputType(purpose string, inputType string) bool {
|
|
878
|
+
if purpose == "" || purpose == "on" || purpose == "off" || inputType == "text" {
|
|
879
|
+
return true
|
|
880
|
+
}
|
|
881
|
+
switch purpose {
|
|
882
|
+
case "email", "username":
|
|
883
|
+
return inputType == "email" || inputType == "search"
|
|
884
|
+
case "url", "photo", "impp":
|
|
885
|
+
return inputType == "url" || inputType == "search"
|
|
886
|
+
case "tel", "tel-country-code", "tel-national", "tel-area-code", "tel-local", "tel-local-prefix", "tel-local-suffix", "tel-extension":
|
|
887
|
+
return inputType == "tel" || inputType == "search"
|
|
888
|
+
case "new-password", "current-password":
|
|
889
|
+
return inputType == "password" || inputType == "search"
|
|
890
|
+
case "bday":
|
|
891
|
+
return inputType == "date" || inputType == "search"
|
|
892
|
+
case "cc-exp":
|
|
893
|
+
return inputType == "month" || inputType == "tel" || inputType == "search"
|
|
894
|
+
case "cc-number", "cc-exp-month", "cc-exp-year", "cc-csc", "transaction-amount", "bday-day", "bday-month", "bday-year":
|
|
895
|
+
return inputType == "number" || inputType == "tel" || inputType == "search"
|
|
896
|
+
default:
|
|
897
|
+
return false
|
|
774
898
|
}
|
|
775
899
|
}
|
|
776
900
|
|
|
@@ -806,7 +930,7 @@ type jsxA11yHeadingHasContent struct{}
|
|
|
806
930
|
|
|
807
931
|
func (jsxA11yHeadingHasContent) Name() string { return "jsx-a11y/heading-has-content" }
|
|
808
932
|
func (jsxA11yHeadingHasContent) Visits() []shimast.Kind {
|
|
809
|
-
return []shimast.Kind{shimast.KindJsxElement}
|
|
933
|
+
return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
|
|
810
934
|
}
|
|
811
935
|
func (jsxA11yHeadingHasContent) Check(ctx *Context, node *shimast.Node) {
|
|
812
936
|
info := jsxElementFromNode(node)
|
|
@@ -826,8 +950,55 @@ func (jsxA11yHtmlHasLang) Visits() []shimast.Kind {
|
|
|
826
950
|
}
|
|
827
951
|
func (jsxA11yHtmlHasLang) Check(ctx *Context, node *shimast.Node) {
|
|
828
952
|
info := jsxElementFromNode(node)
|
|
829
|
-
if info.tag
|
|
830
|
-
|
|
953
|
+
if info.tag != "html" {
|
|
954
|
+
return
|
|
955
|
+
}
|
|
956
|
+
if !jsxHasAttr(info.attrs, "lang") {
|
|
957
|
+
if !info.spread {
|
|
958
|
+
ctx.Report(info.opening, "The html element must have a lang attribute.")
|
|
959
|
+
}
|
|
960
|
+
return
|
|
961
|
+
}
|
|
962
|
+
if attr := info.attrs["lang"]; jsxA11yHtmlLangIsStaticallyFalsy(attr) {
|
|
963
|
+
ctx.Report(attr.node, "The html element must have a non-empty lang attribute.")
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
func jsxA11yHtmlLangIsStaticallyFalsy(attr jsxAttr) bool {
|
|
968
|
+
if attr.node == nil || attr.boolean {
|
|
969
|
+
return false
|
|
970
|
+
}
|
|
971
|
+
jsx := attr.node.AsJsxAttribute()
|
|
972
|
+
if jsx == nil || jsx.Initializer == nil {
|
|
973
|
+
return false
|
|
974
|
+
}
|
|
975
|
+
if jsx.Initializer.Kind == shimast.KindStringLiteral || jsx.Initializer.Kind == shimast.KindNoSubstitutionTemplateLiteral {
|
|
976
|
+
return strings.TrimSpace(stringLiteralText(jsx.Initializer)) == ""
|
|
977
|
+
}
|
|
978
|
+
if jsx.Initializer.Kind != shimast.KindJsxExpression {
|
|
979
|
+
return false
|
|
980
|
+
}
|
|
981
|
+
expression := jsx.Initializer.AsJsxExpression()
|
|
982
|
+
if expression == nil || expression.Expression == nil {
|
|
983
|
+
return true
|
|
984
|
+
}
|
|
985
|
+
switch expression.Expression.Kind {
|
|
986
|
+
case shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral:
|
|
987
|
+
return strings.TrimSpace(stringLiteralText(expression.Expression)) == ""
|
|
988
|
+
case shimast.KindFalseKeyword, shimast.KindNullKeyword:
|
|
989
|
+
return true
|
|
990
|
+
case shimast.KindNumericLiteral:
|
|
991
|
+
value := strings.ReplaceAll(numericLiteralText(expression.Expression), "_", "")
|
|
992
|
+
if integer, err := strconv.ParseInt(value, 0, 64); err == nil {
|
|
993
|
+
return integer == 0
|
|
994
|
+
}
|
|
995
|
+
if unsigned, err := strconv.ParseUint(value, 0, 64); err == nil {
|
|
996
|
+
return unsigned == 0
|
|
997
|
+
}
|
|
998
|
+
number, err := strconv.ParseFloat(value, 64)
|
|
999
|
+
return err == nil && number == 0
|
|
1000
|
+
default:
|
|
1001
|
+
return identifierText(expression.Expression) == "undefined"
|
|
831
1002
|
}
|
|
832
1003
|
}
|
|
833
1004
|
|
|
@@ -910,21 +1081,60 @@ func (jsxA11yLang) Visits() []shimast.Kind {
|
|
|
910
1081
|
}
|
|
911
1082
|
func (jsxA11yLang) Check(ctx *Context, node *shimast.Node) {
|
|
912
1083
|
info := jsxElementFromNode(node)
|
|
913
|
-
|
|
1084
|
+
if info.tag != "html" {
|
|
1085
|
+
return
|
|
1086
|
+
}
|
|
1087
|
+
attr, ok := info.attrs["lang"]
|
|
914
1088
|
if !ok {
|
|
915
1089
|
return
|
|
916
1090
|
}
|
|
917
|
-
|
|
918
|
-
parts := strings.Split(value, "-")
|
|
919
|
-
if value == "" || len(parts[0]) < 2 || len(parts[0]) > 3 {
|
|
1091
|
+
if jsxA11yLangIsInvalid(attr) {
|
|
920
1092
|
ctx.Report(attr.node, "lang must be a valid BCP 47 language tag.")
|
|
921
1093
|
}
|
|
922
1094
|
}
|
|
923
1095
|
|
|
1096
|
+
func jsxA11yLangIsInvalid(attr jsxAttr) bool {
|
|
1097
|
+
if attr.node == nil || attr.boolean {
|
|
1098
|
+
return true
|
|
1099
|
+
}
|
|
1100
|
+
jsx := attr.node.AsJsxAttribute()
|
|
1101
|
+
if jsx == nil || jsx.Initializer == nil {
|
|
1102
|
+
return true
|
|
1103
|
+
}
|
|
1104
|
+
validate := func(value string) bool {
|
|
1105
|
+
if value != strings.TrimSpace(value) {
|
|
1106
|
+
return true
|
|
1107
|
+
}
|
|
1108
|
+
_, err := language.Parse(value)
|
|
1109
|
+
return err != nil
|
|
1110
|
+
}
|
|
1111
|
+
switch jsx.Initializer.Kind {
|
|
1112
|
+
case shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral:
|
|
1113
|
+
return validate(stringLiteralText(jsx.Initializer))
|
|
1114
|
+
case shimast.KindJsxExpression:
|
|
1115
|
+
expression := jsx.Initializer.AsJsxExpression()
|
|
1116
|
+
if expression == nil || expression.Expression == nil {
|
|
1117
|
+
return true
|
|
1118
|
+
}
|
|
1119
|
+
switch expression.Expression.Kind {
|
|
1120
|
+
case shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral:
|
|
1121
|
+
return validate(stringLiteralText(expression.Expression))
|
|
1122
|
+
case shimast.KindTrueKeyword, shimast.KindFalseKeyword, shimast.KindNumericLiteral:
|
|
1123
|
+
return true
|
|
1124
|
+
default:
|
|
1125
|
+
return identifierText(expression.Expression) == "undefined"
|
|
1126
|
+
}
|
|
1127
|
+
default:
|
|
1128
|
+
return false
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
924
1132
|
type jsxA11yMediaHasCaption struct{}
|
|
925
1133
|
|
|
926
|
-
func (jsxA11yMediaHasCaption) Name() string
|
|
927
|
-
func (jsxA11yMediaHasCaption) Visits() []shimast.Kind {
|
|
1134
|
+
func (jsxA11yMediaHasCaption) Name() string { return "jsx-a11y/media-has-caption" }
|
|
1135
|
+
func (jsxA11yMediaHasCaption) Visits() []shimast.Kind {
|
|
1136
|
+
return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
|
|
1137
|
+
}
|
|
928
1138
|
func (jsxA11yMediaHasCaption) Check(ctx *Context, node *shimast.Node) {
|
|
929
1139
|
info := jsxElementFromNode(node)
|
|
930
1140
|
if (info.tag == "audio" || info.tag == "video") && !info.spread && !jsxHasTrackCaption(info.children) {
|
package/linthost/rules_logic.go
CHANGED
|
@@ -49,23 +49,7 @@ func (noExtraBooleanCast) Check(ctx *Context, node *shimast.Node) {
|
|
|
49
49
|
ctx.Report(node, message)
|
|
50
50
|
return
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
argStart := shimscanner.SkipTrivia(src, arg.Pos())
|
|
54
|
-
argEnd := arg.End()
|
|
55
|
-
if argStart < 0 || argStart >= argEnd || argEnd > len(src) {
|
|
56
|
-
ctx.Report(node, message)
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
editPos := shimscanner.SkipTrivia(src, node.Pos())
|
|
60
|
-
if editPos < 0 || editPos >= node.End() {
|
|
61
|
-
ctx.Report(node, message)
|
|
62
|
-
return
|
|
63
|
-
}
|
|
64
|
-
ctx.ReportFix(
|
|
65
|
-
node,
|
|
66
|
-
message,
|
|
67
|
-
TextEdit{Pos: editPos, End: node.End(), Text: src[argStart:argEnd]},
|
|
68
|
-
)
|
|
52
|
+
reportBooleanCastFix(ctx, node, arg, message)
|
|
69
53
|
case shimast.KindPrefixUnaryExpression:
|
|
70
54
|
outer := node.AsPrefixUnaryExpression()
|
|
71
55
|
if outer == nil || outer.Operator != shimast.KindExclamationToken {
|
|
@@ -83,28 +67,88 @@ func (noExtraBooleanCast) Check(ctx *Context, node *shimast.Node) {
|
|
|
83
67
|
return
|
|
84
68
|
}
|
|
85
69
|
message := "Redundant double negation."
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
70
|
+
reportBooleanCastFix(ctx, node, inner.Operand, message)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// reportBooleanCastFix reports a redundant boolean cast covering `node` and,
|
|
75
|
+
// when safe, attaches the autofix that splices the inner expression's source
|
|
76
|
+
// text over the whole cast. Two hazards can make the raw splice unsafe, and
|
|
77
|
+
// both mirror upstream ESLint's fixer (#362):
|
|
78
|
+
//
|
|
79
|
+
// - Precedence. In the two expression-level boolean contexts the rule
|
|
80
|
+
// accepts — `!` operand and ternary condition — a replacement that binds
|
|
81
|
+
// no tighter than the context re-associates: `!Boolean(a && b)` spliced
|
|
82
|
+
// bare becomes `!a && b`, which is `(!a) && b`. The replacement is
|
|
83
|
+
// wrapped in parentheses when the inner expression's precedence is at or
|
|
84
|
+
// below the context's floor. Statement conditions (`if`/`while`/`do`/
|
|
85
|
+
// `for`) need no wrap: the statement's own parentheses contain the
|
|
86
|
+
// result, as do explicit parentheses around the cast itself.
|
|
87
|
+
// - Comments. The splice keeps only the inner expression's text, so a
|
|
88
|
+
// comment anywhere else in the replaced span (`!Boolean(/* why */ ok)`)
|
|
89
|
+
// would be silently deleted. The fix is declined — report only — when
|
|
90
|
+
// the replaced span contains a comment outside the kept text.
|
|
91
|
+
func reportBooleanCastFix(ctx *Context, node, inner *shimast.Node, message string) {
|
|
92
|
+
if inner == nil {
|
|
93
|
+
ctx.Report(node, message)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
src := ctx.File.Text()
|
|
97
|
+
keepStart := shimscanner.SkipTrivia(src, inner.Pos())
|
|
98
|
+
keepEnd := inner.End()
|
|
99
|
+
if keepStart < 0 || keepStart >= keepEnd || keepEnd > len(src) {
|
|
100
|
+
ctx.Report(node, message)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
editPos := shimscanner.SkipTrivia(src, node.Pos())
|
|
104
|
+
if editPos < 0 || editPos >= node.End() || node.End() > len(src) {
|
|
105
|
+
ctx.Report(node, message)
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
if hasCommentBetween(src, editPos, keepStart) ||
|
|
109
|
+
hasCommentBetween(src, keepEnd, node.End()) {
|
|
110
|
+
ctx.Report(node, message)
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
text := src[keepStart:keepEnd]
|
|
114
|
+
if floor, bounded := booleanContextPrecedenceFloor(node); bounded &&
|
|
115
|
+
shimast.GetExpressionPrecedence(inner) <= floor {
|
|
116
|
+
text = "(" + text + ")"
|
|
117
|
+
}
|
|
118
|
+
ctx.ReportFix(node, message, TextEdit{Pos: editPos, End: node.End(), Text: text})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// booleanContextPrecedenceFloor returns the precedence at or below which a
|
|
122
|
+
// replacement spliced over `node` re-associates with its surroundings, and
|
|
123
|
+
// whether such a floor applies at all.
|
|
124
|
+
//
|
|
125
|
+
// Only the two expression-level boolean contexts are bounded: an `!` operand
|
|
126
|
+
// must bind tighter than a unary expression (`!(a && b)`, and the grammar
|
|
127
|
+
// even rejects `!x ** 2` outright), and a ternary condition must bind
|
|
128
|
+
// tighter than the conditional itself (`(x = f()) ? a : b`,
|
|
129
|
+
// `(a ? b : c) ? x : y`). The statement conditions `isInBooleanContext`
|
|
130
|
+
// accepts are unbounded — their mandatory parentheses already contain the
|
|
131
|
+
// replacement — and so is a cast explicitly wrapped in parentheses, because
|
|
132
|
+
// the direct parent is then the ParenthesizedExpression, not the `!` or
|
|
133
|
+
// ternary beyond it.
|
|
134
|
+
func booleanContextPrecedenceFloor(node *shimast.Node) (shimast.OperatorPrecedence, bool) {
|
|
135
|
+
parent := node.Parent
|
|
136
|
+
if parent == nil {
|
|
137
|
+
return shimast.OperatorPrecedenceInvalid, false
|
|
138
|
+
}
|
|
139
|
+
switch parent.Kind {
|
|
140
|
+
case shimast.KindPrefixUnaryExpression:
|
|
141
|
+
prefix := parent.AsPrefixUnaryExpression()
|
|
142
|
+
if prefix != nil && prefix.Operator == shimast.KindExclamationToken {
|
|
143
|
+
return shimast.OperatorPrecedenceUnary, true
|
|
96
144
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return
|
|
145
|
+
case shimast.KindConditionalExpression:
|
|
146
|
+
cond := parent.AsConditionalExpression()
|
|
147
|
+
if cond != nil && cond.Condition == node {
|
|
148
|
+
return shimast.OperatorPrecedenceConditional, true
|
|
101
149
|
}
|
|
102
|
-
ctx.ReportFix(
|
|
103
|
-
node,
|
|
104
|
-
message,
|
|
105
|
-
TextEdit{Pos: editPos, End: node.End(), Text: src[inStart:inEnd]},
|
|
106
|
-
)
|
|
107
150
|
}
|
|
151
|
+
return shimast.OperatorPrecedenceInvalid, false
|
|
108
152
|
}
|
|
109
153
|
|
|
110
154
|
// isInBooleanContext walks up the parent chain to determine whether the
|
|
@@ -267,7 +311,9 @@ func (useIsNaN) Check(ctx *Context, node *shimast.Node) {
|
|
|
267
311
|
}
|
|
268
312
|
|
|
269
313
|
// validTypeof: typeof expressions can only be compared to known type
|
|
270
|
-
// strings. Catches `typeof x === "stirng"`.
|
|
314
|
+
// strings. Catches `typeof x === "stirng"`. Scoped to the four equality
|
|
315
|
+
// operators, like ESLint's own OPERATORS set — a relational comparison such as
|
|
316
|
+
// `typeof x < "m"` orders two strings rather than naming a type.
|
|
271
317
|
// https://eslint.org/docs/latest/rules/valid-typeof
|
|
272
318
|
type validTypeof struct{}
|
|
273
319
|
|
|
@@ -278,7 +324,7 @@ func (validTypeof) Check(ctx *Context, node *shimast.Node) {
|
|
|
278
324
|
if expr == nil || expr.OperatorToken == nil {
|
|
279
325
|
return
|
|
280
326
|
}
|
|
281
|
-
if !
|
|
327
|
+
if !isEqualityOperator(expr.OperatorToken.Kind) {
|
|
282
328
|
return
|
|
283
329
|
}
|
|
284
330
|
left := stripParens(expr.Left)
|
package/linthost/rules_nextjs.go
CHANGED
|
@@ -79,32 +79,121 @@ func nextjsInlineScriptID(ctx *Context, file *shimast.Node) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
func nextjsNextScriptForGA(ctx *Context, file *shimast.Node) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
walkDescendants(file, func(node *shimast.Node) {
|
|
87
|
-
if node.Kind != shimast.KindJsxElement && node.Kind != shimast.KindJsxSelfClosingElement {
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
opening := nextjsOpeningLike(node)
|
|
91
|
-
if !imports.scriptFromNextScript[nextjsJSXName(opening)] {
|
|
82
|
+
nextjsWalkOpeningLike(file, func(opening *shimast.Node) {
|
|
83
|
+
if nextjsJSXName(opening) != "script" {
|
|
92
84
|
return
|
|
93
85
|
}
|
|
94
86
|
src := nextjsJSXAttrString(opening, "src")
|
|
95
|
-
if strings.Contains(src, "
|
|
87
|
+
if strings.Contains(src, "www.google-analytics.com/analytics.js") ||
|
|
88
|
+
strings.Contains(src, "www.googletagmanager.com/gtag/js") {
|
|
96
89
|
ctx.Report(opening, "Use Next.js Google Analytics helpers instead of hand-written gtag scripts.")
|
|
97
90
|
return
|
|
98
91
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
92
|
+
inline := nextjsDangerouslySetInnerHTMLSource(opening)
|
|
93
|
+
if strings.Contains(inline, "www.google-analytics.com/analytics.js") ||
|
|
94
|
+
strings.Contains(inline, "www.googletagmanager.com/gtm.js") {
|
|
95
|
+
ctx.Report(opening, "Use Next.js Google Analytics helpers instead of hand-written gtag scripts.")
|
|
104
96
|
}
|
|
105
97
|
})
|
|
106
98
|
}
|
|
107
99
|
|
|
100
|
+
// nextjsDangerouslySetInnerHTMLSource returns the final static `__html`
|
|
101
|
+
// payload from `dangerouslySetInnerHTML={{ __html: "..." }}`. Object-literal
|
|
102
|
+
// properties are inspected from last to first so an earlier spread or dynamic
|
|
103
|
+
// property cannot obscure a later static assignment, while any later write
|
|
104
|
+
// that might replace `__html` keeps the payload unknown.
|
|
105
|
+
func nextjsDangerouslySetInnerHTMLSource(opening *shimast.Node) string {
|
|
106
|
+
attrNode := nextjsJSXAttr(opening, "dangerouslySetInnerHTML")
|
|
107
|
+
if attrNode == nil {
|
|
108
|
+
return ""
|
|
109
|
+
}
|
|
110
|
+
attr := attrNode.AsJsxAttribute()
|
|
111
|
+
if attr == nil || attr.Initializer == nil || attr.Initializer.Kind != shimast.KindJsxExpression {
|
|
112
|
+
return ""
|
|
113
|
+
}
|
|
114
|
+
container := attr.Initializer.AsJsxExpression()
|
|
115
|
+
if container == nil {
|
|
116
|
+
return ""
|
|
117
|
+
}
|
|
118
|
+
expression := stripParens(container.Expression)
|
|
119
|
+
if expression == nil || expression.Kind != shimast.KindObjectLiteralExpression {
|
|
120
|
+
return ""
|
|
121
|
+
}
|
|
122
|
+
object := expression.AsObjectLiteralExpression()
|
|
123
|
+
if object == nil || object.Properties == nil {
|
|
124
|
+
return ""
|
|
125
|
+
}
|
|
126
|
+
properties := object.Properties.Nodes
|
|
127
|
+
for i := len(properties) - 1; i >= 0; i-- {
|
|
128
|
+
propertyNode := properties[i]
|
|
129
|
+
if propertyNode == nil {
|
|
130
|
+
return ""
|
|
131
|
+
}
|
|
132
|
+
if propertyNode.Kind == shimast.KindSpreadAssignment {
|
|
133
|
+
return ""
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
switch propertyNode.Kind {
|
|
137
|
+
case shimast.KindPropertyAssignment:
|
|
138
|
+
property := propertyNode.AsPropertyAssignment()
|
|
139
|
+
if property == nil {
|
|
140
|
+
return ""
|
|
141
|
+
}
|
|
142
|
+
name, known := nextjsStaticPropertyName(property.Name())
|
|
143
|
+
if !known {
|
|
144
|
+
return ""
|
|
145
|
+
}
|
|
146
|
+
if name == "__html" {
|
|
147
|
+
return stringLiteralText(stripParens(property.Initializer))
|
|
148
|
+
}
|
|
149
|
+
case shimast.KindShorthandPropertyAssignment,
|
|
150
|
+
shimast.KindMethodDeclaration,
|
|
151
|
+
shimast.KindGetAccessor,
|
|
152
|
+
shimast.KindSetAccessor:
|
|
153
|
+
name, known := nextjsStaticPropertyName(propertyNode.Name())
|
|
154
|
+
if !known {
|
|
155
|
+
return ""
|
|
156
|
+
}
|
|
157
|
+
if name == "__html" {
|
|
158
|
+
return ""
|
|
159
|
+
}
|
|
160
|
+
default:
|
|
161
|
+
return ""
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return ""
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
func nextjsStaticPropertyName(node *shimast.Node) (string, bool) {
|
|
168
|
+
if node == nil {
|
|
169
|
+
return "", false
|
|
170
|
+
}
|
|
171
|
+
switch node.Kind {
|
|
172
|
+
case shimast.KindIdentifier:
|
|
173
|
+
return identifierText(node), true
|
|
174
|
+
case shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral:
|
|
175
|
+
return stringLiteralText(node), true
|
|
176
|
+
case shimast.KindNumericLiteral, shimast.KindBigIntLiteral:
|
|
177
|
+
return numericLiteralText(node), true
|
|
178
|
+
case shimast.KindComputedPropertyName:
|
|
179
|
+
computed := node.AsComputedPropertyName()
|
|
180
|
+
if computed == nil {
|
|
181
|
+
return "", false
|
|
182
|
+
}
|
|
183
|
+
expression := stripParens(computed.Expression)
|
|
184
|
+
if expression == nil {
|
|
185
|
+
return "", false
|
|
186
|
+
}
|
|
187
|
+
switch expression.Kind {
|
|
188
|
+
case shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral:
|
|
189
|
+
return stringLiteralText(expression), true
|
|
190
|
+
case shimast.KindNumericLiteral, shimast.KindBigIntLiteral:
|
|
191
|
+
return numericLiteralText(expression), true
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return "", false
|
|
195
|
+
}
|
|
196
|
+
|
|
108
197
|
func nextjsNoAssignModuleVariable(ctx *Context, file *shimast.Node) {
|
|
109
198
|
walkDescendants(file, func(node *shimast.Node) {
|
|
110
199
|
if node.Kind != shimast.KindVariableDeclaration {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// noClassAssign rejects writes to bindings introduced by class declarations
|
|
2
|
+
// and named class expressions. It follows checker symbols instead of names,
|
|
3
|
+
// so references inside the class and in its surrounding scope resolve while
|
|
4
|
+
// parameter, catch, block, and sibling-scope shadows remain independent.
|
|
5
|
+
// https://eslint.org/docs/latest/rules/no-class-assign
|
|
6
|
+
package linthost
|
|
7
|
+
|
|
8
|
+
import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
9
|
+
|
|
10
|
+
type noClassAssign struct{}
|
|
11
|
+
|
|
12
|
+
func (noClassAssign) Name() string { return "no-class-assign" }
|
|
13
|
+
func (noClassAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
14
|
+
func (noClassAssign) NeedsTypeChecker() bool { return true }
|
|
15
|
+
func (noClassAssign) Check(ctx *Context, node *shimast.Node) {
|
|
16
|
+
if ctx == nil || ctx.Checker == nil || node == nil {
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
classSymbols := make(map[*shimast.Symbol]struct{})
|
|
21
|
+
walkDescendants(node, func(child *shimast.Node) {
|
|
22
|
+
name := noClassAssignDeclarationName(child)
|
|
23
|
+
symbol := canonicalValueSymbol(ctx, name)
|
|
24
|
+
if symbol != nil {
|
|
25
|
+
classSymbols[symbol] = struct{}{}
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
if len(classSymbols) == 0 {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
reported := make(map[*shimast.Node]struct{})
|
|
33
|
+
walkDescendants(node, func(child *shimast.Node) {
|
|
34
|
+
for _, target := range writeTargetIdentifiers(child) {
|
|
35
|
+
if _, duplicate := reported[target]; duplicate {
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
symbol := canonicalValueSymbol(ctx, target)
|
|
39
|
+
if _, isClass := classSymbols[symbol]; !isClass {
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
reported[target] = struct{}{}
|
|
43
|
+
ctx.Report(target, "'"+identifierText(target)+"' is a class.")
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// noClassAssignDeclarationName returns the value-binding name introduced by a
|
|
49
|
+
// class declaration or named class expression. Anonymous expressions and
|
|
50
|
+
// anonymous default-export declarations introduce no writable local name.
|
|
51
|
+
func noClassAssignDeclarationName(node *shimast.Node) *shimast.Node {
|
|
52
|
+
if node == nil {
|
|
53
|
+
return nil
|
|
54
|
+
}
|
|
55
|
+
switch node.Kind {
|
|
56
|
+
case shimast.KindClassDeclaration:
|
|
57
|
+
if declaration := node.AsClassDeclaration(); declaration != nil {
|
|
58
|
+
return declaration.Name()
|
|
59
|
+
}
|
|
60
|
+
case shimast.KindClassExpression:
|
|
61
|
+
if expression := node.AsClassExpression(); expression != nil {
|
|
62
|
+
return expression.Name()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return nil
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func init() {
|
|
69
|
+
Register(noClassAssign{})
|
|
70
|
+
}
|