Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import (
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/adjacent_overload_signatures"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/array_type"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/await_thenable"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/camelcase"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/class_literal_property_style"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/consistent_type_assertions"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/member_ordering"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/naming_convention"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_array_delete"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_base_to_string"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_confusing_void_expression"
Expand All @@ -31,6 +35,8 @@ import (
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_namespace"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_redundant_type_constituents"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_require_imports"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_shadow"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_type_alias"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_unnecessary_boolean_literal_compare"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_unnecessary_template_expression"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_unnecessary_type_arguments"
Expand All @@ -43,12 +49,16 @@ import (
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_unsafe_return"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_unsafe_type_assertion"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_unsafe_unary_minus"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_unused_expressions"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_unused_vars"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_use_before_define"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_useless_empty_export"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_var_requires"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/non_nullable_type_assertion_style"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/only_throw_error"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/parameter_properties"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/prefer_as_const"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/prefer_namespace_keyword"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/prefer_promise_reject_errors"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/prefer_reduce_type_parameter"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/prefer_return_this_type"
Expand All @@ -59,6 +69,7 @@ import (
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/restrict_plus_operands"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/restrict_template_expressions"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/return_await"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/sort_type_constituents"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/switch_exhaustiveness_check"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/unbound_method"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/use_unknown_in_catch_callback_variable"
Expand Down Expand Up @@ -333,7 +344,11 @@ func registerAllTypeScriptEslintPluginRules() {
GlobalRuleRegistry.Register("@typescript-eslint/adjacent-overload-signatures", adjacent_overload_signatures.AdjacentOverloadSignaturesRule)
GlobalRuleRegistry.Register("@typescript-eslint/array-type", array_type.ArrayTypeRule)
GlobalRuleRegistry.Register("@typescript-eslint/await-thenable", await_thenable.AwaitThenableRule)
GlobalRuleRegistry.Register("@typescript-eslint/camelcase", camelcase.CamelcaseRule)
GlobalRuleRegistry.Register("@typescript-eslint/class-literal-property-style", class_literal_property_style.ClassLiteralPropertyStyleRule)
GlobalRuleRegistry.Register("@typescript-eslint/consistent-type-assertions", consistent_type_assertions.ConsistentTypeAssertionsRule)
GlobalRuleRegistry.Register("@typescript-eslint/member-ordering", member_ordering.MemberOrderingRule)
GlobalRuleRegistry.Register("@typescript-eslint/naming-convention", naming_convention.NamingConventionRule)
GlobalRuleRegistry.Register("@typescript-eslint/dot-notation", dot_notation.DotNotationRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-array-delete", no_array_delete.NoArrayDeleteRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-base-to-string", no_base_to_string.NoBaseToStringRule)
Expand All @@ -352,6 +367,8 @@ func registerAllTypeScriptEslintPluginRules() {
GlobalRuleRegistry.Register("@typescript-eslint/no-namespace", no_namespace.NoNamespaceRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-redundant-type-constituents", no_redundant_type_constituents.NoRedundantTypeConstituentsRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-require-imports", no_require_imports.NoRequireImportsRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-shadow", no_shadow.NoShadowRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-type-alias", no_type_alias.NoTypeAliasRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-unnecessary-boolean-literal-compare", no_unnecessary_boolean_literal_compare.NoUnnecessaryBooleanLiteralCompareRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-unnecessary-template-expression", no_unnecessary_template_expression.NoUnnecessaryTemplateExpressionRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-unnecessary-type-arguments", no_unnecessary_type_arguments.NoUnnecessaryTypeArgumentsRule)
Expand All @@ -364,12 +381,16 @@ func registerAllTypeScriptEslintPluginRules() {
GlobalRuleRegistry.Register("@typescript-eslint/no-unsafe-return", no_unsafe_return.NoUnsafeReturnRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-unsafe-type-assertion", no_unsafe_type_assertion.NoUnsafeTypeAssertionRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-unsafe-unary-minus", no_unsafe_unary_minus.NoUnsafeUnaryMinusRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-unused-expressions", no_unused_expressions.NoUnusedExpressionsRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-unused-vars", no_unused_vars.NoUnusedVarsRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-use-before-define", no_use_before_define.NoUseBeforeDefineRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-useless-empty-export", no_useless_empty_export.NoUselessEmptyExportRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-var-requires", no_var_requires.NoVarRequiresRule)
GlobalRuleRegistry.Register("@typescript-eslint/non-nullable-type-assertion-style", non_nullable_type_assertion_style.NonNullableTypeAssertionStyleRule)
GlobalRuleRegistry.Register("@typescript-eslint/only-throw-error", only_throw_error.OnlyThrowErrorRule)
GlobalRuleRegistry.Register("@typescript-eslint/parameter-properties", parameter_properties.ParameterPropertiesRule)
GlobalRuleRegistry.Register("@typescript-eslint/prefer-as-const", prefer_as_const.PreferAsConstRule)
GlobalRuleRegistry.Register("@typescript-eslint/prefer-namespace-keyword", prefer_namespace_keyword.PreferNamespaceKeywordRule)
GlobalRuleRegistry.Register("@typescript-eslint/prefer-promise-reject-errors", prefer_promise_reject_errors.PreferPromiseRejectErrorsRule)
GlobalRuleRegistry.Register("@typescript-eslint/prefer-reduce-type-parameter", prefer_reduce_type_parameter.PreferReduceTypeParameterRule)
GlobalRuleRegistry.Register("@typescript-eslint/prefer-return-this-type", prefer_return_this_type.PreferReturnThisTypeRule)
Expand All @@ -380,6 +401,7 @@ func registerAllTypeScriptEslintPluginRules() {
GlobalRuleRegistry.Register("@typescript-eslint/restrict-plus-operands", restrict_plus_operands.RestrictPlusOperandsRule)
GlobalRuleRegistry.Register("@typescript-eslint/restrict-template-expressions", restrict_template_expressions.RestrictTemplateExpressionsRule)
GlobalRuleRegistry.Register("@typescript-eslint/return-await", return_await.ReturnAwaitRule)
GlobalRuleRegistry.Register("@typescript-eslint/sort-type-constituents", sort_type_constituents.SortTypeConstituentsRule)
GlobalRuleRegistry.Register("@typescript-eslint/switch-exhaustiveness-check", switch_exhaustiveness_check.SwitchExhaustivenessCheckRule)
GlobalRuleRegistry.Register("@typescript-eslint/unbound-method", unbound_method.UnboundMethodRule)
GlobalRuleRegistry.Register("@typescript-eslint/use-unknown-in-catch-callback-variable", use_unknown_in_catch_callback_variable.UseUnknownInCatchCallbackVariableRule)
Expand Down
235 changes: 235 additions & 0 deletions internal/plugins/typescript/rules/camelcase/camelcase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package camelcase

import (
"regexp"
"strings"

"github.com/microsoft/typescript-go/shim/ast"
"github.com/web-infra-dev/rslint/internal/rule"
"github.com/web-infra-dev/rslint/internal/utils"
)

// CamelcaseOptions represents the configuration options for the camelcase rule
type CamelcaseOptions struct {
Properties string `json:"properties"` // "always" or "never"
IgnoreDestructuring bool `json:"ignoreDestructuring"`
IgnoreImports bool `json:"ignoreImports"`
IgnoreGlobals bool `json:"ignoreGlobals"`
Allow []string `json:"allow"` // Array of regex patterns to allow
}

var CamelcaseRule = rule.CreateRule(rule.Rule{
Name: "camelcase",
Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
opts := CamelcaseOptions{
Properties: "always",
IgnoreDestructuring: false,
IgnoreImports: false,
IgnoreGlobals: false,
Allow: []string{},
}

// Parse options with dual-format support
if options != nil {
var optsMap map[string]interface{}
var ok bool

if optArray, isArray := options.([]interface{}); isArray && len(optArray) > 0 {
optsMap, ok = optArray[0].(map[string]interface{})
} else {
optsMap, ok = options.(map[string]interface{})
}

if ok {
if properties, ok := optsMap["properties"].(string); ok {
opts.Properties = properties
}
if ignoreDestructuring, ok := optsMap["ignoreDestructuring"].(bool); ok {
opts.IgnoreDestructuring = ignoreDestructuring
}
if ignoreImports, ok := optsMap["ignoreImports"].(bool); ok {
opts.IgnoreImports = ignoreImports
}
if ignoreGlobals, ok := optsMap["ignoreGlobals"].(bool); ok {
opts.IgnoreGlobals = ignoreGlobals
}
if allowVal, ok := optsMap["allow"].([]interface{}); ok {
for _, pattern := range allowVal {
if str, ok := pattern.(string); ok {
opts.Allow = append(opts.Allow, str)
}
}
}
}
}

return rule.RuleListeners{
ast.KindIdentifier: func(node *ast.Node) {
identifier := node.AsIdentifier()
if identifier == nil {
return
}

// Get the identifier name
nameRange := utils.TrimNodeTextRange(ctx.SourceFile, node)
name := ctx.SourceFile.Text()[nameRange.Pos():nameRange.End()]

// Check if name is allowed by regex patterns
if isAllowedByPattern(name, opts.Allow) {
return
}

// TODO: Implement full validation logic:
// 1. Check if identifier is in destructuring and should be ignored
// 2. Check if identifier is an import and should be ignored
// 3. Check if identifier is a global and should be ignored
// 4. Check if identifier is a property and properties="never"
// 5. Validate camelCase format
// 6. Report violations

// Basic camelCase check (incomplete implementation)
if !isCamelCase(name) && !isAllowedByPattern(name, opts.Allow) {
// Only report for identifiers that we should check
// This is a simplified implementation
if shouldCheckIdentifier(node, opts) {
ctx.ReportNode(node, rule.RuleMessage{
Id: "notCamelCase",
Description: "Identifier '" + name + "' is not in camelCase.",
})
}
}
},
}
},
})

func isCamelCase(name string) bool {
// Trim leading and trailing underscores (allowed)
trimmed := strings.Trim(name, "_")

// Empty after trimming is allowed
if len(trimmed) == 0 {
return true
}

// Check if it's all uppercase (CONSTANT_CASE is allowed)
if trimmed == strings.ToUpper(trimmed) {
return true
}

// Check for underscores in the middle (not camelCase/PascalCase)
if strings.Contains(trimmed, "_") {
return false
}

// camelCase or PascalCase (no underscores) is valid
return true
}

func isAllowedByPattern(name string, patterns []string) bool {
for _, pattern := range patterns {
matched, err := regexp.MatchString(pattern, name)
if err == nil && matched {
return true
}
}
return false
}

func shouldCheckIdentifier(node *ast.Node, opts CamelcaseOptions) bool {
if node.Parent == nil {
return false
}

parent := node.Parent
parentKind := parent.Kind

// Check if we're in an import declaration and should ignore
if opts.IgnoreImports {
if isInImportDeclaration(node) {
return false
}
}

// Check if we're in a destructuring pattern and should ignore
if opts.IgnoreDestructuring {
if isInDestructuring(node) {
return false
}
}

// Check if identifier is a property access and properties = "never"
if opts.Properties == "never" {
if isPropertyName(node) {
return false
}
}

// Only check specific contexts where camelcase should apply:
// Variable declarations, function declarations, class declarations, etc.
switch parentKind {
case ast.KindVariableDeclaration,
ast.KindFunctionDeclaration,
ast.KindFunctionExpression,
ast.KindArrowFunction,
ast.KindClassDeclaration,
ast.KindClassExpression,
ast.KindMethodDeclaration,
ast.KindParameter,
ast.KindCatchClause,
ast.KindPropertyDeclaration,
ast.KindPropertySignature,
ast.KindEnumMember:
return true
case ast.KindPropertyAssignment:
// Check properties only if properties = "always"
return opts.Properties == "always"
}

return false
}

func isInImportDeclaration(node *ast.Node) bool {
current := node
for current != nil {
if current.Kind == ast.KindImportDeclaration || current.Kind == ast.KindImportEqualsDeclaration {
return true
}
current = current.Parent
}
return false
}

func isInDestructuring(node *ast.Node) bool {
current := node
for current != nil {
if current.Kind == ast.KindObjectBindingPattern || current.Kind == ast.KindArrayBindingPattern {
return true
}
current = current.Parent
}
return false
}

func isPropertyName(node *ast.Node) bool {
if node.Parent == nil {
return false
}
parent := node.Parent
// Check if this identifier is a property name (not property value)
switch parent.Kind {
case ast.KindPropertyAssignment:
prop := parent.AsPropertyAssignment()
return prop.Name() == node
case ast.KindPropertyDeclaration:
prop := parent.AsPropertyDeclaration()
return prop.Name() == node
case ast.KindMethodDeclaration:
method := parent.AsMethodDeclaration()
return method.Name() == node
case ast.KindPropertySignature:
sig := parent.AsPropertySignatureDeclaration()
return sig.Name() == node
}
return false
}
25 changes: 25 additions & 0 deletions internal/plugins/typescript/rules/camelcase/camelcase_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package camelcase

import (
"testing"

"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/fixtures"
"github.com/web-infra-dev/rslint/internal/rule_tester"
)

func TestCamelcaseRule(t *testing.T) {
rule_tester.RunRuleTester(
fixtures.GetRootDir(),
"tsconfig.json",
t,
&CamelcaseRule,
[]rule_tester.ValidTestCase{
{Code: `const myVariable = 1;`},
{Code: `function myFunction() {}`},
{Code: `class MyClass {}`},
},
[]rule_tester.InvalidTestCase{
// TODO: Add comprehensive invalid test cases
},
)
}
Loading
Loading