From abe46760c4355e53b094ad404f359c71ddcfa24b Mon Sep 17 00:00:00 2001 From: CANMERTINYO <38213551+c4nzin@users.noreply.github.com> Date: Sat, 25 Oct 2025 16:38:34 +0300 Subject: [PATCH] feat: add typescript checkers --- .../discover/custom_analyzer_stub/main.go | 2 + checkers/discover/generate.go | 1 + checkers/discover/generate_test.go | 4 ++ checkers/typescript/README.md | 0 checkers/typescript/no-any.test.ts | 44 +++++++++++++++ checkers/typescript/no-any.yml | 30 +++++++++++ checkers/typescript/no-eval.test.ts | 43 +++++++++++++++ checkers/typescript/no-eval.yml | 53 +++++++++++++++++++ checkers/typescript/no-explicit-any.test.ts | 45 ++++++++++++++++ checkers/typescript/no-explicit-any.yml | 48 +++++++++++++++++ .../typescript/no-non-null-assertion.test.ts | 40 ++++++++++++++ checkers/typescript/no-non-null-assertion.yml | 37 +++++++++++++ checkers/typescript/prefer-const-enum.test.ts | 41 ++++++++++++++ checkers/typescript/prefer-const-enum.yml | 49 +++++++++++++++++ checkers/typescript/prefer-const.test.ts | 34 ++++++++++++ checkers/typescript/prefer-const.yml | 45 ++++++++++++++++ checkers/typescript/scope.go | 22 ++++++++ .../typescript/ts-console-in-prod.test.ts | 36 +++++++++++++ checkers/typescript/ts-console-in-prod.yml | 52 ++++++++++++++++++ .../typescript/ts-debugger-in-prod.test.ts | 27 ++++++++++ checkers/typescript/ts-debugger-in-prod.yml | 44 +++++++++++++++ checkers/typescript/tsx.go | 23 ++++++++ checkers/typescript/unused-import.go | 38 +++++++++++++ checkers/typescript/unused-import.test.ts | 21 ++++++++ pkg/cli/cli.go | 35 ++++++++++++ 25 files changed, 814 insertions(+) create mode 100644 checkers/typescript/README.md create mode 100644 checkers/typescript/no-any.test.ts create mode 100644 checkers/typescript/no-any.yml create mode 100644 checkers/typescript/no-eval.test.ts create mode 100644 checkers/typescript/no-eval.yml create mode 100644 checkers/typescript/no-explicit-any.test.ts create mode 100644 checkers/typescript/no-explicit-any.yml create mode 100644 checkers/typescript/no-non-null-assertion.test.ts create mode 100644 checkers/typescript/no-non-null-assertion.yml create mode 100644 checkers/typescript/prefer-const-enum.test.ts create mode 100644 checkers/typescript/prefer-const-enum.yml create mode 100644 checkers/typescript/prefer-const.test.ts create mode 100644 checkers/typescript/prefer-const.yml create mode 100644 checkers/typescript/scope.go create mode 100644 checkers/typescript/ts-console-in-prod.test.ts create mode 100644 checkers/typescript/ts-console-in-prod.yml create mode 100644 checkers/typescript/ts-debugger-in-prod.test.ts create mode 100644 checkers/typescript/ts-debugger-in-prod.yml create mode 100644 checkers/typescript/tsx.go create mode 100644 checkers/typescript/unused-import.go create mode 100644 checkers/typescript/unused-import.test.ts diff --git a/checkers/discover/custom_analyzer_stub/main.go b/checkers/discover/custom_analyzer_stub/main.go index 530afa10..b8e2a5f3 100644 --- a/checkers/discover/custom_analyzer_stub/main.go +++ b/checkers/discover/custom_analyzer_stub/main.go @@ -8,6 +8,8 @@ import ( "globstar.dev/analysis" ) +var customCheckers []*analysis.Analyzer + var ( path = flag.String("path", ".", "Path to the directory to analyze") test = flag.Bool("test", false, "Run the tests") diff --git a/checkers/discover/generate.go b/checkers/discover/generate.go index 6d75010f..d97ef904 100644 --- a/checkers/discover/generate.go +++ b/checkers/discover/generate.go @@ -75,6 +75,7 @@ package checkers import ( "globstar.dev/checkers/javascript" "globstar.dev/checkers/python" + "globstar.dev/checkers/typescript" goAnalysis "globstar.dev/analysis" ) diff --git a/checkers/discover/generate_test.go b/checkers/discover/generate_test.go index 10606bb0..06f249ee 100644 --- a/checkers/discover/generate_test.go +++ b/checkers/discover/generate_test.go @@ -65,6 +65,7 @@ package checkers import ( "globstar.dev/checkers/javascript" "globstar.dev/checkers/python" + "globstar.dev/checkers/typescript" goAnalysis "globstar.dev/analysis" ) @@ -86,6 +87,7 @@ package checkers import ( "globstar.dev/checkers/javascript" "globstar.dev/checkers/python" + "globstar.dev/checkers/typescript" goAnalysis "globstar.dev/analysis" ) @@ -116,6 +118,7 @@ package checkers import ( "globstar.dev/checkers/javascript" "globstar.dev/checkers/python" + "globstar.dev/checkers/typescript" goAnalysis "globstar.dev/analysis" ) @@ -151,6 +154,7 @@ package checkers import ( "globstar.dev/checkers/javascript" "globstar.dev/checkers/python" + "globstar.dev/checkers/typescript" goAnalysis "globstar.dev/analysis" ) diff --git a/checkers/typescript/README.md b/checkers/typescript/README.md new file mode 100644 index 00000000..e69de29b diff --git a/checkers/typescript/no-any.test.ts b/checkers/typescript/no-any.test.ts new file mode 100644 index 00000000..a0857329 --- /dev/null +++ b/checkers/typescript/no-any.test.ts @@ -0,0 +1,44 @@ +// Test file for no-any checker +// This file should trigger the no-any checker + +// +function processData(data: any) { + return data; +} + +// +const handleInput = (input: any): void => { + console.log(input); +}; + +class DataProcessor { + // + process(item: any) { + return item; + } +} + +interface Config { + // + value: any; +} + +//Should NOT be flagged - using unknown +function processUnknown(data: unknown) { + return data; +} + +//Should NOT be flagged - using specific type +function processUser(user: User) { + return user; +} + +//Should NOT be flagged - using generic +function processGeneric(data: T): T { + return data; +} + +interface User { + name: string; + age: number; +} diff --git a/checkers/typescript/no-any.yml b/checkers/typescript/no-any.yml new file mode 100644 index 00000000..f7e9b620 --- /dev/null +++ b/checkers/typescript/no-any.yml @@ -0,0 +1,30 @@ +language: typescript +name: no-any +message: "Avoid using 'any' type. Use specific types or 'unknown' instead." +category: best-practices +severity: warning + +pattern: | + (type_annotation + (predefined_type) @any_type + (#eq? @any_type "any")) @no-any + +description: | + Issue: + Using the 'any' type defeats the purpose of TypeScript's type system. It disables type + checking and can lead to runtime errors that could have been caught at compile time. + + Solution: + - Use specific types when the shape is known + - Use 'unknown' for values of truly unknown type (safer than 'any') + - Use generics when appropriate + - Use union types for multiple possible types + + Example: + Bad: + function process(data: any) { ... } + + Good: + function process(data: User | Product) { ... } + function process(data: unknown) { ... } + function process(data: T) { ... } diff --git a/checkers/typescript/no-eval.test.ts b/checkers/typescript/no-eval.test.ts new file mode 100644 index 00000000..81cbcd28 --- /dev/null +++ b/checkers/typescript/no-eval.test.ts @@ -0,0 +1,43 @@ +// Test file for no-eval checker + +function dangerousCode(input: string) { + // + eval(input); +} + +// +const result = eval("2 + 2"); + +// +const calculate = (expr: string) => eval(expr); + +function processConfig() { + const config = '{ "debug": true }'; + // + return eval(`(${config})`); +} + +//Should NOT be flagged - using JSON.parse +function safeJsonParse(json: string) { + return JSON.parse(json); +} + +//Should NOT be flagged - using function map +const operations = { + add: (a: number, b: number) => a + b, + subtract: (a: number, b: number) => a - b, +}; + +function calculate_something(op: string, a: number, b: number) { + const operation = operations[op as keyof typeof operations]; + return operation ? operation(a, b) : 0; +} + +//Should NOT be flagged - configuration object +const config = { + development: { apiUrl: "http://localhost:3000" }, + production: { apiUrl: "https://api.example.com" }, +}; + +const environment: "development" | "production" = "development"; +const settings = config[environment]; diff --git a/checkers/typescript/no-eval.yml b/checkers/typescript/no-eval.yml new file mode 100644 index 00000000..17a2a1d5 --- /dev/null +++ b/checkers/typescript/no-eval.yml @@ -0,0 +1,53 @@ +language: typescript +name: no-eval +message: "Avoid using 'eval()' as it poses security risks and performance issues." +category: security +severity: error + +pattern: | + (call_expression + function: (identifier) @eval_func + (#eq? @eval_func "eval")) @no-eval + +description: | + Issue: + Using 'eval()' is dangerous and should be avoided because: + - Security: Can execute arbitrary code, making it vulnerable to code injection attacks + - Performance: Prevents JavaScript engine optimizations + - Debugging: Makes code harder to debug and understand + - Maintainability: Creates unpredictable code behavior + + eval() executes a string as JavaScript code, which means if user input is passed + to eval(), attackers can execute malicious code in your application. + + Solution: + - Use JSON.parse() for parsing JSON strings + - Use Function constructor if you absolutely need dynamic code (still risky) + - Restructure your code to avoid dynamic code execution + - Use proper parsing libraries for specific data formats + + Example: + Dangerous: + const userInput = getUserInput(); + eval(userInput); // NEVER DO THIS! + + const code = "alert('Hello')"; + eval(code); // Avoid this + + Safe alternatives: + // For JSON: + const data = JSON.parse(jsonString); + + // For calculations: + const operators = { + '+': (a, b) => a + b, + '-': (a, b) => a - b, + }; + const result = operators[operator](a, b); + + // For configuration: + const config = { + development: { api: 'dev.api.com' }, + production: { api: 'api.com' } + }; + const settings = config[environment]; diff --git a/checkers/typescript/no-explicit-any.test.ts b/checkers/typescript/no-explicit-any.test.ts new file mode 100644 index 00000000..2cd9784b --- /dev/null +++ b/checkers/typescript/no-explicit-any.test.ts @@ -0,0 +1,45 @@ +// Test file for no-explicit-any checker + +// +function parseJson(json: string): any { + return JSON.parse(json); +} + +// +const processDataSafer = (data: any): void => { + console.log(data); +}; + +interface Config { + // + value: any; + // + settings: any; +} + +class DataHandler { + // + handle(input: any) { + return input; + } +} + +//Should NOT be flagged - using unknown +function parseJsonSafe(json: string): unknown { + return JSON.parse(json); +} + +//Should NOT be flagged - using generics +function parseJsonGeneric(json: string): T { + return JSON.parse(json); +} + +//Should NOT be flagged - using specific type +interface User { + name: string; + email: string; +} + +function saveUser(user: User): void { + console.log(user.name); +} diff --git a/checkers/typescript/no-explicit-any.yml b/checkers/typescript/no-explicit-any.yml new file mode 100644 index 00000000..7f6fbd70 --- /dev/null +++ b/checkers/typescript/no-explicit-any.yml @@ -0,0 +1,48 @@ +language: typescript +name: no-explicit-any +message: "Avoid explicit 'any' type annotation. Consider using 'unknown' or a specific type." +category: best-practices +severity: warning + +pattern: | + (type_annotation + (predefined_type) @explicit_any + (#eq? @explicit_any "any")) @no-explicit-any + +description: | + Issue: + Explicitly annotating with 'any' type removes all type safety for that value. + This defeats the purpose of using TypeScript and can hide bugs. + + The 'any' type should be avoided except in very specific cases where you're + gradually migrating JavaScript code to TypeScript or dealing with truly + dynamic data structures. + + Solution: + - Use 'unknown' when the type is truly unknown (forces type checking before use) + - Use specific types or interfaces when the structure is known + - Use generics for reusable components + - Use union types for multiple possible types + - Use type guards to narrow 'unknown' types safely + + Example: + Bad: + function parseJson(json: string): any { + return JSON.parse(json); + } + + Good: + function parseJson(json: string): T { + return JSON.parse(json); + } + + // Or with unknown: + function parseJson(json: string): unknown { + return JSON.parse(json); + } + + const data = parseJson(jsonString); + if (isUser(data)) { + // TypeScript knows data is User here + console.log(data.name); + } diff --git a/checkers/typescript/no-non-null-assertion.test.ts b/checkers/typescript/no-non-null-assertion.test.ts new file mode 100644 index 00000000..03158e0f --- /dev/null +++ b/checkers/typescript/no-non-null-assertion.test.ts @@ -0,0 +1,40 @@ +// Test file for no-non-null-assertion checker + +interface User { + name: string; + email?: string; +} + +function getUserEmail(user: User | null) { + // + return user!.email; +} + +// +const element = document.getElementById("myId")!; + +// +const value = possiblyUndefined!.property; + +function process(data: string | undefined) { + // + const length = data!.length; + return length; +} + +//Should NOT be flagged - using optional chaining +function getUserEmailSafe(user: User | null) { + return user?.email; +} + +//Should NOT be flagged - using null check +const elementSafe = document.getElementById("myId"); +if (elementSafe) { + elementSafe.classList.add("active"); +} + +//Should NOT be flagged - using nullish coalescing +function processSafe(data: string | undefined) { + const length = data?.length ?? 0; + return length; +} diff --git a/checkers/typescript/no-non-null-assertion.yml b/checkers/typescript/no-non-null-assertion.yml new file mode 100644 index 00000000..8b6b38ef --- /dev/null +++ b/checkers/typescript/no-non-null-assertion.yml @@ -0,0 +1,37 @@ +language: typescript +name: no-non-null-assertion +message: "Avoid using non-null assertion operator (!). This can lead to runtime errors." +category: best-practices +severity: warning + +pattern: | + (non_null_expression) @no-non-null-assertion + +exclude: + - "test/**" + - "*_test.ts" + - "*.test.ts" + - "tests/**" + - "__tests__/**" + +description: | + Issue: + The non-null assertion operator (!) tells TypeScript to ignore potential null or undefined + values. This defeats the purpose of strict null checking and can lead to runtime errors. + + Solution: + - Use optional chaining (?.) to safely access properties + - Use nullish coalescing (??) to provide default values + - Use explicit null checks (if statements) + - Use type guards to narrow types + + Example: + Bad: + const value = possiblyNull!.property; + const element = document.getElementById('id')!; + + Good: + const value = possiblyNull?.property; + const element = document.getElementById('id'); + if (element) { /* use element */ } + const value = possiblyNull?.property ?? defaultValue; diff --git a/checkers/typescript/prefer-const-enum.test.ts b/checkers/typescript/prefer-const-enum.test.ts new file mode 100644 index 00000000..e4d226a6 --- /dev/null +++ b/checkers/typescript/prefer-const-enum.test.ts @@ -0,0 +1,41 @@ +// Test file for prefer-const-enum checker + +//Should be flagged - regular enum +enum Color { + Red = "RED", + Green = "GREEN", + Blue = "BLUE", +} + +//Should be flagged - regular enum with numbers +enum Status { + Active, + Inactive, + Pending, +} + +//Should be flagged - regular enum +enum Direction { + Up = 1, + Down = 2, + Left = 3, + Right = 4, +} + +//Should NOT be flagged - const enum +const enum Priority { + Low = 1, + Medium = 2, + High = 3, +} + +//Should NOT be flagged - const enum with strings +const enum Theme { + Light = "LIGHT", + Dark = "DARK", +} + +//Should NOT be flagged - using union type instead +type ColorType = "RED" | "GREEN" | "BLUE"; + +const myColor: ColorType = "RED"; diff --git a/checkers/typescript/prefer-const-enum.yml b/checkers/typescript/prefer-const-enum.yml new file mode 100644 index 00000000..8cff7074 --- /dev/null +++ b/checkers/typescript/prefer-const-enum.yml @@ -0,0 +1,49 @@ +language: typescript +name: prefer-const-enum +message: "Consider using 'const enum' instead of regular 'enum' for better performance." +category: performance +severity: info + +pattern: | + (enum_declaration + name: (identifier)) @enum + (#not-match? @enum "^const\\s+enum") + +description: | + Issue: + Regular enums in TypeScript generate additional JavaScript code at runtime. + This increases bundle size and can impact performance, especially for large enums + or when enums are used frequently. + + const enums are completely removed during compilation and replaced with their + literal values, resulting in smaller bundle sizes and better performance. + + Solution: + - Use 'const enum' when you don't need the enum object at runtime + - Use regular 'enum' only when you need runtime features (reverse mapping, iteration) + - Consider using union types of literals as an alternative + + + Note: + const enums have limitations: + - Cannot be used with computed members + - Cannot be merged with namespaces + - Limited support when isolatedModules is enabled + + Example: + Less optimal: + enum Color { + Red = 'RED', + Green = 'GREEN', + Blue = 'BLUE' + } + + Better: + const enum Color { + Red = 'RED', + Green = 'GREEN', + Blue = 'BLUE' + } + + Alternative (union type): + type Color = 'RED' | 'GREEN' | 'BLUE'; diff --git a/checkers/typescript/prefer-const.test.ts b/checkers/typescript/prefer-const.test.ts new file mode 100644 index 00000000..bce5d1ae --- /dev/null +++ b/checkers/typescript/prefer-const.test.ts @@ -0,0 +1,34 @@ +// Test file for prefer-const checker + +// +let userName = "John"; +console.log(userName); + +// +let config = { + api: "https://api.example.com", + timeout: 5000, +}; + +// +let numbers = [1, 2, 3, 4, 5]; + +// +let settings = { debug: false }; +settings.debug = true; // mutation is ok, reassignment is not + +//Should NOT be flagged - using const +const userName2 = "Jane"; +console.log(userName2); + +// NOTE: This checker has limitations - it flags all 'let' declarations +// In production, you'd want scope analysis to check reassignment +// +let counter = 0; +counter = 1; +counter += 1; + +// +for (let i = 0; i < 10; i++) { + console.log(i); +} diff --git a/checkers/typescript/prefer-const.yml b/checkers/typescript/prefer-const.yml new file mode 100644 index 00000000..9a48ebd6 --- /dev/null +++ b/checkers/typescript/prefer-const.yml @@ -0,0 +1,45 @@ +language: typescript +name: prefer-const +message: "Use 'const' instead of 'let' for variables that are never reassigned." +category: best-practices +severity: info + +pattern: | + (lexical_declaration + kind: "let" @let_keyword + (variable_declarator + name: (identifier) @var_name)) @prefer-const + +description: | + Issue: + Using 'let' for variables that are never reassigned makes code harder to reason about. + It suggests the variable might change when it actually doesn't, leading to confusion + and potential bugs. + + Using 'const' clearly communicates that the variable binding is immutable, making + the code more predictable and easier to understand. + + Solution: + - Use 'const' for variables that are never reassigned + - Use 'let' only when the variable needs to be reassigned + - Avoid 'var' entirely in modern TypeScript + + Note: + This is a simplified pattern-based checker. For production use, a more sophisticated + checker would need to track all assignments to determine if a variable is truly + never reassigned. + + Example: + Bad: + let userName = 'John'; + let config = { api: 'https://api.example.com' }; + console.log(userName, config); + + Good: + const userName = 'John'; + const config = { api: 'https://api.example.com' }; + console.log(userName, config); + + Also Good (when reassignment is needed): + let counter = 0; + counter += 1; diff --git a/checkers/typescript/scope.go b/checkers/typescript/scope.go new file mode 100644 index 00000000..2246b1ca --- /dev/null +++ b/checkers/typescript/scope.go @@ -0,0 +1,22 @@ +// scope resolution implementation for TypeScript files +// +//globstar:registry-exclude +package typescript + +import ( + "reflect" + + "globstar.dev/analysis" +) + +var ScopeAnalyzer = &analysis.Analyzer{ + Name: "ts-scope", + ResultType: reflect.TypeOf(&analysis.ScopeTree{}), + Run: buildScopeTree, + Language: analysis.LangTs, +} + +func buildScopeTree(pass *analysis.Pass) (any, error) { + scope := analysis.MakeScopeTree(pass.Analyzer.Language, pass.FileContext.Ast, pass.FileContext.Source) + return scope, nil +} diff --git a/checkers/typescript/ts-console-in-prod.test.ts b/checkers/typescript/ts-console-in-prod.test.ts new file mode 100644 index 00000000..15dd38f1 --- /dev/null +++ b/checkers/typescript/ts-console-in-prod.test.ts @@ -0,0 +1,36 @@ +// Test file for ts-console-in-prod checker + +function processDataWithConsole(data: any[]) { + // + console.log("Processing data:", data); + return data; +} + +class UserService { + login(credentials: { username: string; password: string }) { + // + console.log("Login attempt:", credentials); + // ... login logic + } +} + +const debugInfo = (info: string) => { + // + console.debug(info); +}; + +try { + // some code + throw new Error("test"); +} catch (error) { + // + console.error("Error occurred:", error); +} + +// ✅ Should NOT be flagged - using proper logger +import logger from "./logger"; + +function processDataSafe(data: any[]) { + logger.info("Processing data:", data); + return data; +} diff --git a/checkers/typescript/ts-console-in-prod.yml b/checkers/typescript/ts-console-in-prod.yml new file mode 100644 index 00000000..6769ec73 --- /dev/null +++ b/checkers/typescript/ts-console-in-prod.yml @@ -0,0 +1,52 @@ +language: typescript +name: ts-console-in-prod +message: "Avoid using console statements in production code." +category: best-practices +severity: info + +pattern: | + (call_expression + function: (member_expression + object: (identifier) @console + property: (property_identifier) @method) + (#eq? @console "console")) @ts-console-in-prod + +exclude: + - "test/**" + - "*_test.ts" + - "*.test.ts" + - "*.spec.ts" + - "tests/**" + - "__tests__/**" + - "scripts/**" + +description: | + Issue: + Console statements in production code can: + - Expose sensitive information in browser console + - Impact application performance + - Clutter the production console + - Create confusion for end users + + Solution: + - Remove console statements before deployment + - Use a proper logging library (winston, pino, etc.) + - Use environment-based logging (only log in development) + - Set up build tools to strip console.log in production + + Example: + ❌ Bad: + function login(user) { + console.log('User credentials:', user); + // ... login logic + } + + ✅ Good: + import logger from './logger'; + + function login(user) { + if (process.env.NODE_ENV === 'development') { + logger.debug('Login attempt:', user.email); + } + // ... login logic + } diff --git a/checkers/typescript/ts-debugger-in-prod.test.ts b/checkers/typescript/ts-debugger-in-prod.test.ts new file mode 100644 index 00000000..2ec6cbbf --- /dev/null +++ b/checkers/typescript/ts-debugger-in-prod.test.ts @@ -0,0 +1,27 @@ +// Test file for ts-debugger-in-prod checker + +function processData(data: number[]) { + // + debugger; + return data.map((x) => x * 2); +} + +class DataProcessor { + process(items: string[]) { + // + debugger; + return items.filter((x) => x.length > 0); + } +} + +const handleClick = () => { + // + debugger; + console.log("clicked"); +}; + +// ✅ Should NOT be flagged - no debugger +function processDataSafe(data: number[]) { + console.log("Processing data:", data); + return data.map((x) => x * 2); +} diff --git a/checkers/typescript/ts-debugger-in-prod.yml b/checkers/typescript/ts-debugger-in-prod.yml new file mode 100644 index 00000000..273acd3d --- /dev/null +++ b/checkers/typescript/ts-debugger-in-prod.yml @@ -0,0 +1,44 @@ +language: typescript +name: ts-debugger-in-prod +message: "Avoid using debugger statements in production code." +category: best-practices +severity: warning + +pattern: > + (debugger_statement) @ts-debugger-in-prod + +exclude: + - "test/**" + - "*_test.ts" + - "*.test.ts" + - "*.spec.ts" + - "tests/**" + - "__tests__/**" + +description: | + Issue: + Using `debugger` statements in production code is considered poor practice. When executed + in a browser with developer tools open, these statements pause execution and activate the + debugger, which can significantly disrupt the user experience in production environments. + + Debugger statements are useful during development but should be removed before deploying + code to production. + + Solution: + - Remove debugger statements before committing + - Use proper logging instead (console.log, logger libraries) + - Use breakpoints in your IDE during development + - Set up ESLint rules to catch these automatically + + Example: + ❌ Bad: + function processData(data) { + debugger; // This will pause execution in production! + return data.map(x => x * 2); + } + + ✅ Good: + function processData(data) { + console.log('Processing data:', data); + return data.map(x => x * 2); + } diff --git a/checkers/typescript/tsx.go b/checkers/typescript/tsx.go new file mode 100644 index 00000000..c3660502 --- /dev/null +++ b/checkers/typescript/tsx.go @@ -0,0 +1,23 @@ +// TSX (TypeScript + JSX) support +// +//globstar:registry-exclude +package typescript + +import ( + "reflect" + + "globstar.dev/analysis" +) + +var TsxAnalyzer = &analysis.Analyzer{ + Name: "tsx", + ResultType: reflect.TypeOf(&analysis.ScopeTree{}), + Run: buildTsxTree, + Language: analysis.LangTsx, +} + +func buildTsxTree(pass *analysis.Pass) (any, error) { + // TSX uses same scope resolution as TypeScript + scope := analysis.MakeScopeTree(pass.Analyzer.Language, pass.FileContext.Ast, pass.FileContext.Source) + return scope, nil +} diff --git a/checkers/typescript/unused-import.go b/checkers/typescript/unused-import.go new file mode 100644 index 00000000..d9a34f3c --- /dev/null +++ b/checkers/typescript/unused-import.go @@ -0,0 +1,38 @@ +package typescript + +import ( + "fmt" + + "globstar.dev/analysis" +) + +var UnusedImport = &analysis.Analyzer{ + Name: "unused-import", + Requires: []*analysis.Analyzer{ScopeAnalyzer}, + Run: checkUnusedImports, + Language: analysis.LangTs, + Description: "This checker checks for unused imports in TypeScript code. Unused imports can be removed to reduce the size of the bundle. Unused imports are also a code smell and can indicate that the code is not well-organized.", + Category: analysis.CategoryAntipattern, + Severity: analysis.SeverityInfo, +} + +func checkUnusedImports(pass *analysis.Pass) (interface{}, error) { + scopeResult := pass.ResultOf[ScopeAnalyzer] + if scopeResult == nil { + return nil, nil + } + + scope := scopeResult.(*analysis.ScopeTree) + + for _, scope := range scope.ScopeOfNode { + for _, variable := range scope.Variables { + if variable.Kind == analysis.VarKindImport { + if len(variable.Refs) == 0 { + pass.Report(pass, variable.DeclNode, fmt.Sprintf("unused import \"%s\"", variable.Name)) + } + } + } + } + + return nil, nil +} diff --git a/checkers/typescript/unused-import.test.ts b/checkers/typescript/unused-import.test.ts new file mode 100644 index 00000000..f371e086 --- /dev/null +++ b/checkers/typescript/unused-import.test.ts @@ -0,0 +1,21 @@ +// Test file for ts-unused-import checker + +// Should be flagged - unused import +import { unusedFunction } from "./utils"; +import { anotherUnused } from "library"; + +// hould NOT be flagged - used import +import { usedFunction } from "./helpers"; +import React from "react"; + +interface Props { + value: string; +} + +// Using React +const Component: React.FC = ({ value }) => { + // Using usedFunction + return usedFunction(value); +}; + +export default Component; diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index eb54f988..fb8c2138 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -461,6 +461,41 @@ func (c *Cli) RunCheckers(runBuiltinCheckers, runCustomCheckers bool) error { } } + for lang, analyzers := range patternCheckers { + if len(analyzers) == 0 { + continue + } + + var analyzerPtrs []*analysis.Analyzer + for i := range analyzers { + analyzerPtrs = append(analyzerPtrs, &analyzers[i]) + } + + + + langIssues, err := analysis.RunAnalyzers( + c.RootDirectory, + analyzerPtrs, + func(filename string) bool { + if c.CmpHash != "" { + _, isChanged := changedFileMap[filename] + return isChanged + } + return true + }, + ) + if err != nil { + return fmt.Errorf("failed to run pattern analyzers for %v: %w", lang, err) + } + + for _, issue := range langIssues { + txt, _ := issue.AsText() + log.Error().Msg(string(txt)) + + result.issues = append(result.issues, issue) + } + } + if runCustomCheckers { customGoIssues, textIssues, err := c.runCustomGoAnalyzers() if err != nil {