-
Notifications
You must be signed in to change notification settings - Fork 2
Feat: Enhance rule set #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
d812ea1
Implment no-concat-string; specify-type;
meowbmw a9be3dd
minor fixup
meowbmw e92316a
Refactor no-concat-string using built-in type information to deduce s…
meowbmw 5bd993f
fix minor spelling issue
meowbmw a9f9ef5
fix sample_eslint.config.mjs
meowbmw 5db6cfd
specifyType only checks for float now; changed test case a bit
meowbmw 41e8e0a
remove redundant test cases and change markdown wording (wasm perform…
meowbmw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,6 @@ | |
| dist/** | ||
| reference/** | ||
| sample_cases/** | ||
| sample_config/** | ||
| build/** | ||
| wasm-toolchain/** | ||
| coverage/** | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ typechecker | |
| tsconfig | ||
| tseslint | ||
| tses | ||
| sonarjs | ||
|
|
||
| // AssemblyScript types and keywords | ||
| i8 | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| # no-concat-string | ||
|
|
||
| > Disallow string concatenation in loops | ||
|
|
||
| ## Rule Details | ||
|
|
||
| String concatenation inside loops can lead to performance issues in AssemblyScript. Each concatenation operation creates a new string object, which can cause memory allocation overhead and garbage collection pressure in tight loops. This rule enforces using alternative approaches like array joining or string builders for better performance. | ||
|
|
||
| ## Rule Options | ||
|
|
||
| This rule has no configuration options. | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Incorrect | ||
|
|
||
| ```ts | ||
| // String concatenation with + operator in loop | ||
| for (let i = 0; i < 1000; i++) { | ||
| const message = "Count: " + i; // not recommended | ||
| } | ||
|
|
||
| // String concatenation with += operator in loop | ||
| let result = ""; | ||
| for (let i = 0; i < 1000; i++) { | ||
| result += "Item " + i; // not recommended | ||
| } | ||
|
|
||
| // String methods used in concatenation | ||
| let formatted = ""; | ||
| for (let i = 0; i < items.length; i++) { | ||
| formatted += text.toUpperCase() + " "; // not recommended | ||
| } | ||
|
|
||
| // Object property string concatenation | ||
| const obj = { message: "" }; | ||
| for (let i = 0; i < 10; i++) { | ||
| obj.message += "test"; // not recommended | ||
| } | ||
| ``` | ||
|
|
||
| ### Correct | ||
|
|
||
| ```ts | ||
| // Use array join for better performance | ||
| const parts: string[] = []; | ||
| for (let i = 0; i < 1000; i++) { | ||
| // add items to parts | ||
| // ... | ||
| } | ||
| const result = parts.join(""); | ||
|
|
||
| // String concatenation outside loops is allowed | ||
| const greeting = "Hello " + name; | ||
|
|
||
| // String operations that don't involve concatenation | ||
| for (let i = 0; i < items.length; i++) { | ||
| const formatted = items[i].toString(); | ||
| console.log(formatted); | ||
| } | ||
| ``` | ||
|
|
||
| ## When Not To Use | ||
|
|
||
| If you're not concerned about performance in specific use cases or working with very small loops where the performance impact is negligible, you might choose to disable this rule. However, it's generally recommended to follow this rule for better performance. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| # specify-type | ||
|
|
||
| > Enforce explicit type annotations for variable declarations | ||
|
|
||
| ## Rule Details | ||
|
|
||
| In AssemblyScript, explicit type annotations are crucial for WebAssembly compilation and performance optimization. This rule enforces that variables have explicit type annotations, especially for numeric types where JavaScript's dynamic typing doesn't distinguish between different WebAssembly numeric types (i32, f32, f64, etc.). | ||
|
|
||
| ## Rule Options | ||
|
|
||
| This rule has no configuration options. | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Incorrect | ||
|
|
||
| ```ts | ||
| // Missing type annotation for numeric literal | ||
| const mileage = 5.3; // not recommended | ||
|
|
||
| // No type annotation and no initialization | ||
| let count; // not recommended | ||
|
|
||
| // Array with numeric literals without type annotation | ||
| const numbers = [1, 2, 3]; // not recommended | ||
|
|
||
| // Non-const variables without type annotation | ||
| let value = "hello"; // not recommended | ||
| var index = 0; // not recommended | ||
| ``` | ||
|
|
||
| ### Correct | ||
|
|
||
| ```ts | ||
| // Explicit type annotation for numeric values | ||
| const mileage: f32 = 5.3; // recommended | ||
|
|
||
| // Explicit type annotation when no initialization | ||
| let count: i32; // recommended | ||
|
|
||
| // Array with explicit type annotation | ||
| const numbers: i32[] = [1, 2, 3]; // recommended | ||
| const numbers: i32[] = new Array<i32>(); // recommended | ||
|
|
||
| // Non-const variables with explicit type annotations | ||
| let value: string = "hello"; // recommended | ||
| var index: i32 = 0; // recommended | ||
|
|
||
| // Const variables with non-numeric types (type can be inferred) | ||
| const message = "hello"; // allowed for string literals | ||
| const isValid = true; // allowed for boolean literals | ||
| ``` | ||
|
|
||
| ## Rationale | ||
|
|
||
| - **WebAssembly Type Safety**: AssemblyScript compiles to WebAssembly, which requires explicit typing | ||
| - **Performance**: Explicit types help the compiler generate optimized WebAssembly code | ||
|
xpirad marked this conversation as resolved.
Outdated
|
||
| - **Numeric Type Distinction**: JavaScript numbers don't distinguish between i32, f32, f64, etc. | ||
| - **Code Clarity**: Explicit types make the intended data types clear to developers | ||
|
|
||
| ## When Not To Use | ||
|
|
||
| This rule is specifically designed for AssemblyScript development. If you're writing regular TypeScript/JavaScript code, you might find this rule too restrictive. However, for AssemblyScript projects, explicit typing is strongly recommended for optimal compilation results. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,193 @@ | ||
| import { TSESTree, AST_NODE_TYPES } from "@typescript-eslint/utils"; | ||
| import createRule from "../utils/createRule.js"; | ||
|
|
||
| /** | ||
| * Rule: Don't allow string concatenation in loops as this can incur performance penalties. | ||
| * Bad: | ||
| * for (let i = 0; i < 1000; i++) { | ||
| * str += "Hello" + i; // String concatenation in loop | ||
| * } | ||
| * Good: | ||
| * for (let i = 0; i < 1000; i++) { | ||
| * arr.push("Hello" + i); | ||
| * } | ||
| * const str = arr.join(""); | ||
| */ | ||
|
|
||
| // Helper function to determine if a node is likely a string | ||
| // this is NOT 100% inclusive | ||
| function isStringType(node: TSESTree.Node) { | ||
| // String literals | ||
| if (node.type === AST_NODE_TYPES.Literal && typeof node.value === "string") { | ||
| return true; | ||
| } | ||
|
|
||
| // Template literals | ||
| if (node.type === AST_NODE_TYPES.TemplateLiteral) { | ||
| return true; | ||
| } | ||
|
|
||
| // String() constructor | ||
| if ( | ||
| node.type === AST_NODE_TYPES.CallExpression && | ||
| node.callee.type === AST_NODE_TYPES.Identifier && | ||
| node.callee.name === "String" | ||
| ) { | ||
| return true; | ||
| } | ||
|
|
||
| // String methods that return strings (e.g., str.substring()) | ||
| if ( | ||
| node.type === AST_NODE_TYPES.CallExpression && | ||
| node.callee.type === AST_NODE_TYPES.MemberExpression && | ||
| [ | ||
| "charAt", | ||
| "concat", | ||
| "normalize", | ||
| "repeat", | ||
| "replace", | ||
| "replaceAll", | ||
| "slice", | ||
| "substring", | ||
| "toLowerCase", | ||
| "toUpperCase", | ||
| "trim", | ||
| "trimEnd", | ||
| "trimStart", | ||
| ].includes( | ||
| node.callee.property.type === AST_NODE_TYPES.Identifier | ||
| ? node.callee.property.name | ||
| : "" | ||
| ) | ||
| ) { | ||
| return true; | ||
| } | ||
|
|
||
| // Binary expression that likely results in a string | ||
| // eslint-disable-next-line sonarjs/prefer-single-boolean-return | ||
| if ( | ||
| node.type === AST_NODE_TYPES.BinaryExpression && | ||
| node.operator === "+" && | ||
| (isStringType(node.left) || isStringType(node.right)) | ||
| ) { | ||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
| export default createRule({ | ||
| name: "no-concat-string", | ||
| meta: { | ||
| type: "problem", | ||
| docs: { | ||
| description: "Disallow string concatenation in loops", | ||
| }, | ||
| messages: { | ||
| noConcatInLoop: | ||
| "String concatenation inside loops can lead to performance issues. Use array.join() or a string builder instead.", | ||
|
xpirad marked this conversation as resolved.
|
||
| }, | ||
| schema: [], // no options | ||
| }, | ||
| defaultOptions: [], | ||
| create(context) { | ||
| // Track the loop nesting level | ||
| let loopDepth = 0; | ||
| // Track string variables | ||
| const stringVariables = new Set(); | ||
|
|
||
| return { | ||
| // Track entry and exit for loops | ||
| ForStatement() { | ||
| loopDepth++; | ||
| }, | ||
| "ForStatement:exit"() { | ||
| loopDepth--; | ||
| }, | ||
|
|
||
| WhileStatement() { | ||
| loopDepth++; | ||
| }, | ||
| "WhileStatement:exit"() { | ||
| loopDepth--; | ||
| }, | ||
|
|
||
| DoWhileStatement() { | ||
| loopDepth++; | ||
| }, | ||
| "DoWhileStatement:exit"() { | ||
| loopDepth--; | ||
| }, | ||
|
|
||
| ForInStatement() { | ||
| loopDepth++; | ||
| }, | ||
| "ForInStatement:exit"() { | ||
| loopDepth--; | ||
| }, | ||
|
|
||
| ForOfStatement() { | ||
| loopDepth++; | ||
| }, | ||
| "ForOfStatement:exit"() { | ||
| loopDepth--; | ||
| }, | ||
|
|
||
| // Track string variable declarations | ||
| VariableDeclarator(node) { | ||
| if ( | ||
| node.init && | ||
| node.init.type === AST_NODE_TYPES.Literal && | ||
| typeof node.init.value === "string" && | ||
| node.id.type === AST_NODE_TYPES.Identifier | ||
| ) { | ||
| stringVariables.add(node.id.name); | ||
| } | ||
| }, | ||
|
|
||
| // Check for string concatenation with + operator | ||
| BinaryExpression(node) { | ||
|
xpirad marked this conversation as resolved.
|
||
| // Only check inside loops | ||
| if (loopDepth === 0) return; | ||
|
xpirad marked this conversation as resolved.
|
||
|
|
||
| if ( | ||
| node.operator === "+" && | ||
| (isStringType(node.left) || isStringType(node.right)) | ||
| ) { | ||
| context.report({ | ||
| node, | ||
| messageId: "noConcatInLoop", | ||
| }); | ||
| } | ||
| }, | ||
|
|
||
| // Check for string concatenation with += operator | ||
| AssignmentExpression(node) { | ||
| if (loopDepth === 0) return; | ||
|
|
||
| if (node.operator === "+=") { | ||
| // Check if right side is a string type | ||
| const rightIsString = isStringType(node.right); | ||
|
|
||
| // Check different left side patterns | ||
| let shouldReport = false; | ||
|
|
||
| if (node.left.type === AST_NODE_TYPES.Identifier) { | ||
| // Handle: variable += "string" | ||
| shouldReport = stringVariables.has(node.left.name) || rightIsString; | ||
| } else if (node.left.type === AST_NODE_TYPES.MemberExpression) { | ||
| // Handle: obj.prop += "string" or obj["key"] += "string" | ||
| // For member expressions, we assume if right side is string, it's likely string concatenation | ||
| shouldReport = rightIsString; | ||
| } | ||
|
|
||
| if (shouldReport) { | ||
| context.report({ | ||
| node, | ||
| messageId: "noConcatInLoop", | ||
| }); | ||
| } | ||
| } | ||
| }, | ||
| }; | ||
| }, | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.