mimo-lang 1.1.1 → 2.0.6

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.
Files changed (165) hide show
  1. package/.gitattributes +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +71 -39
  4. package/adapters/browserAdapter.js +86 -0
  5. package/adapters/nodeAdapter.js +101 -0
  6. package/bin/cli.js +80 -0
  7. package/bin/commands/convert.js +27 -0
  8. package/bin/commands/doctor.js +139 -0
  9. package/bin/commands/eval.js +39 -0
  10. package/bin/commands/fmt.js +109 -0
  11. package/bin/commands/help.js +72 -0
  12. package/bin/commands/lint.js +117 -0
  13. package/bin/commands/repl.js +24 -0
  14. package/bin/commands/run.js +64 -0
  15. package/bin/commands/test.js +126 -0
  16. package/bin/utils/colors.js +38 -0
  17. package/bin/utils/formatError.js +47 -0
  18. package/bin/utils/fs.js +57 -0
  19. package/bin/utils/version.js +8 -0
  20. package/build.js +18 -0
  21. package/bun.lock +74 -0
  22. package/index.js +48 -77
  23. package/index.web.js +364 -0
  24. package/interpreter/BuiltinFunction.js +32 -0
  25. package/interpreter/ErrorHandler.js +120 -0
  26. package/interpreter/ExpressionEvaluator.js +106 -0
  27. package/interpreter/Interpreter.js +172 -0
  28. package/interpreter/MimoError.js +112 -0
  29. package/interpreter/ModuleLoader.js +236 -0
  30. package/interpreter/StatementExecutor.js +107 -0
  31. package/interpreter/Utils.js +82 -0
  32. package/interpreter/Values.js +87 -0
  33. package/interpreter/coreBuiltins.js +490 -0
  34. package/interpreter/environment.js +99 -0
  35. package/interpreter/evaluators/binaryExpressionEvaluator.js +111 -0
  36. package/interpreter/evaluators/collectionEvaluator.js +151 -0
  37. package/interpreter/evaluators/functionCallEvaluator.js +76 -0
  38. package/interpreter/evaluators/literalEvaluator.js +27 -0
  39. package/interpreter/evaluators/moduleAccessEvaluator.js +25 -0
  40. package/interpreter/evaluators/templateLiteralEvaluator.js +20 -0
  41. package/interpreter/executors/BaseExecutor.js +37 -0
  42. package/interpreter/executors/ControlFlowExecutor.js +206 -0
  43. package/interpreter/executors/FunctionExecutor.js +126 -0
  44. package/interpreter/executors/PatternMatchExecutor.js +93 -0
  45. package/interpreter/executors/VariableExecutor.js +144 -0
  46. package/interpreter/index.js +8 -0
  47. package/interpreter/stdlib/array/accessFunctions.js +61 -0
  48. package/interpreter/stdlib/array/arrayUtils.js +36 -0
  49. package/interpreter/stdlib/array/higherOrderFunctions.js +285 -0
  50. package/interpreter/stdlib/array/searchFunctions.js +77 -0
  51. package/interpreter/stdlib/array/setFunctions.js +49 -0
  52. package/interpreter/stdlib/array/transformationFunctions.js +68 -0
  53. package/interpreter/stdlib/array.js +85 -0
  54. package/interpreter/stdlib/assert.js +143 -0
  55. package/interpreter/stdlib/datetime.js +170 -0
  56. package/interpreter/stdlib/env.js +54 -0
  57. package/interpreter/stdlib/fs.js +161 -0
  58. package/interpreter/stdlib/http.js +92 -0
  59. package/interpreter/stdlib/json.js +70 -0
  60. package/interpreter/stdlib/math.js +309 -0
  61. package/interpreter/stdlib/object.js +142 -0
  62. package/interpreter/stdlib/path.js +69 -0
  63. package/interpreter/stdlib/regex.js +134 -0
  64. package/interpreter/stdlib/string.js +260 -0
  65. package/interpreter/suggestions.js +46 -0
  66. package/lexer/Lexer.js +245 -0
  67. package/lexer/TokenTypes.js +131 -0
  68. package/lexer/createToken.js +11 -0
  69. package/lexer/tokenizers/commentTokenizer.js +45 -0
  70. package/lexer/tokenizers/literalTokenizer.js +163 -0
  71. package/lexer/tokenizers/symbolTokenizer.js +69 -0
  72. package/lexer/tokenizers/whitespaceTokenizer.js +36 -0
  73. package/package.json +29 -13
  74. package/parser/ASTNodes.js +448 -0
  75. package/parser/Parser.js +188 -0
  76. package/parser/expressions/atomicExpressions.js +165 -0
  77. package/parser/expressions/conditionalExpressions.js +0 -0
  78. package/parser/expressions/operatorExpressions.js +79 -0
  79. package/parser/expressions/primaryExpressions.js +77 -0
  80. package/parser/parseStatement.js +184 -0
  81. package/parser/parserExpressions.js +115 -0
  82. package/parser/parserUtils.js +19 -0
  83. package/parser/statements/controlFlowParsers.js +106 -0
  84. package/parser/statements/functionParsers.js +314 -0
  85. package/parser/statements/moduleParsers.js +57 -0
  86. package/parser/statements/patternMatchParsers.js +124 -0
  87. package/parser/statements/variableParsers.js +155 -0
  88. package/repl.js +325 -0
  89. package/test.js +47 -0
  90. package/tools/PrettyPrinter.js +3 -0
  91. package/tools/convert/Args.js +46 -0
  92. package/tools/convert/Registry.js +91 -0
  93. package/tools/convert/Transpiler.js +78 -0
  94. package/tools/convert/plugins/README.md +66 -0
  95. package/tools/convert/plugins/alya/index.js +10 -0
  96. package/tools/convert/plugins/alya/to_alya.js +289 -0
  97. package/tools/convert/plugins/alya/visitors/expressions.js +257 -0
  98. package/tools/convert/plugins/alya/visitors/statements.js +403 -0
  99. package/tools/convert/plugins/base_converter.js +228 -0
  100. package/tools/convert/plugins/javascript/index.js +10 -0
  101. package/tools/convert/plugins/javascript/mimo_runtime.js +265 -0
  102. package/tools/convert/plugins/javascript/to_js.js +155 -0
  103. package/tools/convert/plugins/javascript/visitors/expressions.js +197 -0
  104. package/tools/convert/plugins/javascript/visitors/patterns.js +102 -0
  105. package/tools/convert/plugins/javascript/visitors/statements.js +236 -0
  106. package/tools/convert/plugins/python/index.js +10 -0
  107. package/tools/convert/plugins/python/mimo_runtime.py +811 -0
  108. package/tools/convert/plugins/python/to_py.js +329 -0
  109. package/tools/convert/plugins/python/visitors/expressions.js +272 -0
  110. package/tools/convert/plugins/python/visitors/patterns.js +100 -0
  111. package/tools/convert/plugins/python/visitors/statements.js +257 -0
  112. package/tools/convert.js +102 -0
  113. package/tools/format/CommentAttacher.js +190 -0
  114. package/tools/format/CommentLexer.js +152 -0
  115. package/tools/format/Printer.js +849 -0
  116. package/tools/format/config.js +107 -0
  117. package/tools/formatter.js +169 -0
  118. package/tools/lint/Linter.js +391 -0
  119. package/tools/lint/config.js +114 -0
  120. package/tools/lint/rules/consistent-return.js +62 -0
  121. package/tools/lint/rules/max-depth.js +56 -0
  122. package/tools/lint/rules/no-empty-function.js +45 -0
  123. package/tools/lint/rules/no-magic-numbers.js +46 -0
  124. package/tools/lint/rules/no-shadow.js +113 -0
  125. package/tools/lint/rules/no-unused-vars.js +26 -0
  126. package/tools/lint/rules/prefer-const.js +19 -0
  127. package/tools/linter.js +261 -0
  128. package/tools/replFormatter.js +93 -0
  129. package/tools/stamp-version.js +32 -0
  130. package/web/index.js +9 -0
  131. package/bun.lockb +0 -0
  132. package/cli.js +0 -84
  133. package/compiler/execute/interpreter.js +0 -68
  134. package/compiler/execute/interpreters/binary.js +0 -12
  135. package/compiler/execute/interpreters/call.js +0 -10
  136. package/compiler/execute/interpreters/if.js +0 -10
  137. package/compiler/execute/interpreters/try-catch.js +0 -10
  138. package/compiler/execute/interpreters/while.js +0 -8
  139. package/compiler/execute/utils/createfunction.js +0 -11
  140. package/compiler/execute/utils/evaluate.js +0 -20
  141. package/compiler/execute/utils/operate.js +0 -23
  142. package/compiler/lexer/processToken.js +0 -40
  143. package/compiler/lexer/tokenTypes.js +0 -4
  144. package/compiler/lexer/tokenizer.js +0 -74
  145. package/compiler/parser/expression/comparison.js +0 -18
  146. package/compiler/parser/expression/identifier.js +0 -29
  147. package/compiler/parser/expression/number.js +0 -10
  148. package/compiler/parser/expression/operator.js +0 -21
  149. package/compiler/parser/expression/punctuation.js +0 -31
  150. package/compiler/parser/expression/string.js +0 -6
  151. package/compiler/parser/parseExpression.js +0 -27
  152. package/compiler/parser/parseStatement.js +0 -34
  153. package/compiler/parser/parser.js +0 -45
  154. package/compiler/parser/statement/call.js +0 -26
  155. package/compiler/parser/statement/function.js +0 -29
  156. package/compiler/parser/statement/if.js +0 -34
  157. package/compiler/parser/statement/return.js +0 -10
  158. package/compiler/parser/statement/set.js +0 -11
  159. package/compiler/parser/statement/show.js +0 -10
  160. package/compiler/parser/statement/try-catch.js +0 -25
  161. package/compiler/parser/statement/while.js +0 -22
  162. package/converter/go/convert.js +0 -110
  163. package/converter/js/convert.js +0 -107
  164. package/jsconfig.json +0 -27
  165. package/vite.config.js +0 -17
@@ -0,0 +1,114 @@
1
+ /**
2
+ * tools/lint/config.js
3
+ *
4
+ * Loads per-project lint configuration from a `.mimorc` file.
5
+ *
6
+ * File format (JSON):
7
+ * {
8
+ * "rules": {
9
+ * "no-unused-vars": true,
10
+ * "prefer-const": false,
11
+ * "no-magic-numbers": { "severity": "error", "allow": [0, 1, 2] },
12
+ * "max-depth": { "max": 4 }
13
+ * }
14
+ * }
15
+ *
16
+ * The resolved config is merged in this priority order (highest wins):
17
+ * CLI --rule flags > .mimorc > DEFAULT_RULES in linter.js
18
+ *
19
+ * loadConfig() is designed to be safe in non-Node environments (bundlers,
20
+ * playground): it accepts an optional `readFileFn` so callers can inject
21
+ * a filesystem reader. When no reader is provided it returns an empty
22
+ * config object (no file system access attempted).
23
+ */
24
+
25
+ /**
26
+ * Attempt to load and parse a `.mimorc` file located at `configPath`.
27
+ *
28
+ * @param {string} configPath - Absolute path to the `.mimorc` file.
29
+ * @param {Function|null} readFileFn - A sync `(path) => string` reader,
30
+ * e.g. `(p) => fs.readFileSync(p, 'utf-8')`. Pass `null` or omit to
31
+ * skip file I/O (safe for bundled/browser environments).
32
+ * @returns {{ rules: Object }} Parsed config, or `{ rules: {} }` on any
33
+ * failure (missing file, bad JSON, wrong environment).
34
+ */
35
+ export function loadConfig(configPath, readFileFn = null) {
36
+ if (typeof readFileFn !== 'function') {
37
+ return { rules: {} };
38
+ }
39
+
40
+ let raw;
41
+ try {
42
+ raw = readFileFn(configPath);
43
+ } catch {
44
+ // File does not exist or is unreadable — silently return empty config
45
+ return { rules: {} };
46
+ }
47
+
48
+ let parsed;
49
+ try {
50
+ parsed = JSON.parse(raw);
51
+ } catch (e) {
52
+ throw new Error(`.mimorc: invalid JSON — ${e.message}`);
53
+ }
54
+
55
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
56
+ throw new Error('.mimorc: root must be a JSON object');
57
+ }
58
+
59
+ const rules = parsed.rules;
60
+ if (rules !== undefined && (typeof rules !== 'object' || Array.isArray(rules))) {
61
+ throw new Error('.mimorc: "rules" must be an object');
62
+ }
63
+
64
+ return { rules: rules || {} };
65
+ }
66
+
67
+ /**
68
+ * Merge rule configs in priority order.
69
+ * Later arguments take precedence over earlier ones.
70
+ *
71
+ * Each argument is a `{ ruleId: true | false | {...options} }` map.
72
+ *
73
+ * @param {...Object} layers
74
+ * @returns {Object}
75
+ */
76
+ export function mergeRuleConfigs(...layers) {
77
+ const result = {};
78
+ for (const layer of layers) {
79
+ if (!layer || typeof layer !== 'object') continue;
80
+ for (const [ruleId, value] of Object.entries(layer)) {
81
+ result[ruleId] = value;
82
+ }
83
+ }
84
+ return result;
85
+ }
86
+
87
+ /**
88
+ * Find the `.mimorc` file by walking up from `startDir` to `rootDir`.
89
+ * Returns the path of the first `.mimorc` found, or `null`.
90
+ *
91
+ * @param {string} startDir
92
+ * @param {string} rootDir
93
+ * @param {Function} readFileFn - Same sync reader as in loadConfig().
94
+ * @param {Function} pathJoinFn - `(a, b) => string` path joiner.
95
+ * @returns {string|null}
96
+ */
97
+ export function findConfigFile(startDir, rootDir, readFileFn, pathJoinFn) {
98
+ if (typeof readFileFn !== 'function' || typeof pathJoinFn !== 'function') return null;
99
+
100
+ let dir = startDir;
101
+ while (true) {
102
+ const candidate = pathJoinFn(dir, '.mimorc');
103
+ try {
104
+ readFileFn(candidate);
105
+ return candidate; // readable → found
106
+ } catch {
107
+ // not here
108
+ }
109
+ const parent = pathJoinFn(dir, '..');
110
+ if (parent === dir || dir === rootDir) break;
111
+ dir = parent;
112
+ }
113
+ return null;
114
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * consistent-return
3
+ *
4
+ * Requires that functions either always return a value, or never return a
5
+ * value. Mixing `return` (bare) and `return <expr>` in the same function
6
+ * is confusing and often a bug.
7
+ *
8
+ * Uses entry/exit listeners to track per-function state on a stack.
9
+ */
10
+
11
+ export const consistentReturn = {
12
+ meta: {
13
+ type: 'problem',
14
+ description: 'Require consistent use of `return` in functions',
15
+ defaultSeverity: 'warning',
16
+ },
17
+ create: (context) => {
18
+ /**
19
+ * Stack of per-function frames.
20
+ * Each frame: { node, hasValueReturn: bool, hasVoidReturn: bool }
21
+ */
22
+ const stack = [];
23
+
24
+ function enterFunction(node) {
25
+ stack.push({ node, hasValueReturn: false, hasVoidReturn: false });
26
+ }
27
+
28
+ function exitFunction() {
29
+ const frame = stack.pop();
30
+ if (!frame) return;
31
+
32
+ if (frame.hasValueReturn && frame.hasVoidReturn) {
33
+ const label = frame.node.name
34
+ ? `Function '${frame.node.name}'`
35
+ : 'Anonymous function';
36
+ context.report({
37
+ node: frame.node,
38
+ message: `${label} inconsistently returns a value — some paths return a value and others do not.`,
39
+ });
40
+ }
41
+ }
42
+
43
+ return {
44
+ FunctionDeclaration: enterFunction,
45
+ 'FunctionDeclaration:exit': exitFunction,
46
+
47
+ AnonymousFunction: enterFunction,
48
+ 'AnonymousFunction:exit': exitFunction,
49
+
50
+ ReturnStatement: (node) => {
51
+ const frame = stack[stack.length - 1];
52
+ if (!frame) return;
53
+
54
+ if (node.argument !== null && node.argument !== undefined) {
55
+ frame.hasValueReturn = true;
56
+ } else {
57
+ frame.hasVoidReturn = true;
58
+ }
59
+ },
60
+ };
61
+ },
62
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * max-depth
3
+ *
4
+ * Enforces a maximum depth for nested block-creating constructs (if, while,
5
+ * for, loop, match/case, try). Helps prevent deeply-nested, hard-to-read code.
6
+ *
7
+ * Options:
8
+ * max {number} — maximum allowed nesting depth (default: 4)
9
+ *
10
+ * Example config:
11
+ * "max-depth": { "max": 3 }
12
+ */
13
+
14
+ /** Node types that increase the nesting depth tracked by LinterScopeTracker. */
15
+ const BLOCK_TYPES = new Set([
16
+ 'IfStatement',
17
+ 'WhileStatement',
18
+ 'ForStatement',
19
+ 'LoopStatement',
20
+ 'MatchStatement',
21
+ 'CaseClause',
22
+ 'TryStatement',
23
+ ]);
24
+
25
+ export const maxDepth = {
26
+ meta: {
27
+ type: 'suggestion',
28
+ description: 'Enforce a maximum depth for nested blocks',
29
+ defaultSeverity: 'warning',
30
+ },
31
+ create: (context) => {
32
+ const maxAllowed = typeof context.options?.max === 'number'
33
+ ? context.options.max
34
+ : 4;
35
+
36
+ return {
37
+ // Use a generic visitor that fires for all block-creating node types.
38
+ // We check the depth *after* enterNode has already incremented it,
39
+ // so the current depth equals the depth of this node.
40
+ ...Object.fromEntries(
41
+ [...BLOCK_TYPES].map(nodeType => [
42
+ nodeType,
43
+ (node) => {
44
+ const depth = context.getScope().getCurrentDepth();
45
+ if (depth > maxAllowed) {
46
+ context.report({
47
+ node,
48
+ message: `Block nesting depth (${depth}) exceeds the maximum allowed (${maxAllowed}).`,
49
+ });
50
+ }
51
+ },
52
+ ])
53
+ ),
54
+ };
55
+ },
56
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * no-empty-function
3
+ *
4
+ * Disallows function declarations and anonymous functions whose body contains
5
+ * no executable statements.
6
+ *
7
+ * An empty body is defined as `body` being an empty array, or an array that
8
+ * contains only comment-like nodes (nodes without a `type` property, e.g.
9
+ * null/undefined elements). A body with any typed AST node is not empty.
10
+ */
11
+
12
+ function isEmptyBody(body) {
13
+ if (!Array.isArray(body) || body.length === 0) return true;
14
+ // Consider the body non-empty if at least one element is a real AST node.
15
+ return !body.some(item => item && typeof item === 'object' && item.type);
16
+ }
17
+
18
+ export const noEmptyFunction = {
19
+ meta: {
20
+ type: 'suggestion',
21
+ description: 'Disallow empty function bodies',
22
+ defaultSeverity: 'warning',
23
+ },
24
+ create: (context) => {
25
+ return {
26
+ FunctionDeclaration: (node) => {
27
+ if (isEmptyBody(node.body)) {
28
+ context.report({
29
+ node,
30
+ message: `Function '${node.name}' has an empty body.`,
31
+ });
32
+ }
33
+ },
34
+
35
+ AnonymousFunction: (node) => {
36
+ if (isEmptyBody(node.body)) {
37
+ context.report({
38
+ node,
39
+ message: 'Anonymous function has an empty body.',
40
+ });
41
+ }
42
+ },
43
+ };
44
+ },
45
+ };
@@ -0,0 +1,46 @@
1
+ // Default set of numbers that are commonly allowed (and not "magic").
2
+ // This can be extended via rule options: { allow: [0, 1, 2, ...] }
3
+ const DEFAULT_ALLOWED = [0, 1, -1, 10, 100, 1000];
4
+
5
+ export const noMagicNumbers = {
6
+ meta: {
7
+ type: 'suggestion',
8
+ description: 'Disallow magic numbers - prefer named constants',
9
+ defaultSeverity: 'warning',
10
+ },
11
+ create: (context) => {
12
+ // Merge default allowed list with any user-supplied values.
13
+ const allowList = Array.isArray(context.options?.allow)
14
+ ? new Set([...DEFAULT_ALLOWED, ...context.options.allow])
15
+ : new Set(DEFAULT_ALLOWED);
16
+
17
+ return {
18
+ // This listener will run for every Literal node the linter encounters.
19
+ Literal: (node) => {
20
+ // We only care about numeric literals.
21
+ if (typeof node.value !== 'number') {
22
+ return;
23
+ }
24
+
25
+ // Ignore numbers that are commonly used and not considered "magic".
26
+ if (allowList.has(node.value)) {
27
+ return;
28
+ }
29
+
30
+ // Get the parent node to check the context.
31
+ const parent = context.getParent(node);
32
+
33
+ // Allow numbers if they are the direct value of a `const` declaration.
34
+ if (parent?.type === 'VariableDeclaration' && parent.kind === 'const') {
35
+ return;
36
+ }
37
+
38
+ // If it's a "magic number", report it.
39
+ context.report({
40
+ node: node,
41
+ message: `Magic number '${node.value}' detected. Consider declaring it as a named constant.`,
42
+ });
43
+ }
44
+ };
45
+ }
46
+ };
@@ -0,0 +1,113 @@
1
+ /**
2
+ * no-shadow
3
+ *
4
+ * Disallows variable or function declarations that shadow a binding from an
5
+ * outer scope. Shadowing makes code harder to reason about and can hide bugs.
6
+ *
7
+ * Checks:
8
+ * - `let` / `const` / `global` VariableDeclarations
9
+ * - FunctionDeclarations (the function name itself)
10
+ * - Function parameters (declared inside FunctionDeclaration / AnonymousFunction)
11
+ */
12
+
13
+ export const noShadow = {
14
+ meta: {
15
+ type: 'problem',
16
+ description: 'Disallow variable declarations that shadow variables in outer scopes',
17
+ defaultSeverity: 'warning',
18
+ },
19
+ create: (context) => {
20
+ /**
21
+ * Check whether `name` already exists in any *ancestor* scope (not the
22
+ * current scope, since the declaration hasn't been committed yet when the
23
+ * node-entry listener fires for VariableDeclaration, but the scope tracker
24
+ * has already called enterNode which calls declare() — so we look for
25
+ * an owning scope that is an ancestor of the current scope).
26
+ */
27
+ function isShadowing(name) {
28
+ const tracker = context.getScope();
29
+ const current = tracker.getCurrentScope();
30
+
31
+ // Walk up the parent chain looking for the binding.
32
+ let ancestor = current.parent;
33
+ while (ancestor) {
34
+ if (ancestor.variables.has(name)) return true;
35
+ ancestor = ancestor.parent;
36
+ }
37
+ return false;
38
+ }
39
+
40
+ return {
41
+ VariableDeclaration: (node) => {
42
+ if (typeof node.identifier !== 'string') return; // destructuring — skip
43
+ if (node.kind === 'set') return; // re-assignment, not a new binding
44
+
45
+ if (isShadowing(node.identifier)) {
46
+ context.report({
47
+ node,
48
+ message: `'${node.identifier}' shadows a variable from an outer scope.`,
49
+ });
50
+ }
51
+ },
52
+
53
+ FunctionDeclaration: (node) => {
54
+ // The function name is declared in the parent scope, so we check
55
+ // whether any *grandparent-or-higher* scope owns the same name.
56
+ const tracker = context.getScope();
57
+ const current = tracker.getCurrentScope(); // already the new fn scope
58
+
59
+ // Function name lives in current.parent (the scope that surrounds the fn).
60
+ // We want to know if current.parent.parent or higher has the same name.
61
+ let ancestor = current.parent?.parent;
62
+ while (ancestor) {
63
+ if (ancestor.variables.has(node.name)) {
64
+ context.report({
65
+ node,
66
+ message: `Function '${node.name}' shadows a variable from an outer scope.`,
67
+ });
68
+ break;
69
+ }
70
+ ancestor = ancestor.parent;
71
+ }
72
+
73
+ // Also check parameters — they are declared in the function's own scope
74
+ // (current), so check current.parent and above.
75
+ const paramScope = current.parent;
76
+ (node.params || []).forEach(param => {
77
+ let a = paramScope;
78
+ while (a) {
79
+ if (a.variables.has(param.name)) {
80
+ context.report({
81
+ node: param,
82
+ message: `Parameter '${param.name}' shadows a variable from an outer scope.`,
83
+ });
84
+ break;
85
+ }
86
+ a = a.parent;
87
+ }
88
+ });
89
+ },
90
+
91
+ AnonymousFunction: (node) => {
92
+ // Parameters in the function's scope — check parent and above.
93
+ const tracker = context.getScope();
94
+ const current = tracker.getCurrentScope(); // the anonymous fn scope
95
+ const paramScope = current.parent;
96
+
97
+ (node.params || []).forEach(param => {
98
+ let a = paramScope;
99
+ while (a) {
100
+ if (a.variables.has(param.name)) {
101
+ context.report({
102
+ node: param,
103
+ message: `Parameter '${param.name}' shadows a variable from an outer scope.`,
104
+ });
105
+ break;
106
+ }
107
+ a = a.parent;
108
+ }
109
+ });
110
+ },
111
+ };
112
+ },
113
+ };
@@ -0,0 +1,26 @@
1
+ export const noUnusedVars = {
2
+ meta: {
3
+ type: 'suggestion',
4
+ description: 'Disallow unused variables and function parameters',
5
+ },
6
+ create: (context) => {
7
+ return {
8
+ // This runs once after the entire file has been traversed.
9
+ Program_exit: (programNode) => {
10
+ const unused = context.getScope().getUnusedVariables();
11
+ unused.forEach(variable => {
12
+ let message;
13
+ if (variable.kind === 'parameter') {
14
+ message = `Parameter '${variable.name}' is defined but never used.`;
15
+ } else {
16
+ message = `Variable '${variable.name}' is defined but never used.`;
17
+ }
18
+ context.report({
19
+ node: variable.node,
20
+ message: message,
21
+ });
22
+ });
23
+ }
24
+ };
25
+ }
26
+ };
@@ -0,0 +1,19 @@
1
+ export const preferConst = {
2
+ meta: {
3
+ type: 'suggestion',
4
+ description: 'Suggest using `const` for variables that are never reassigned.',
5
+ },
6
+ create: (context) => {
7
+ return {
8
+ Program_exit: (programNode) => {
9
+ const unmutatedLets = context.getScope().getUnmutatedLets();
10
+ unmutatedLets.forEach(variable => {
11
+ context.report({
12
+ node: variable.node,
13
+ message: `Variable '${variable.name}' is never reassigned. Use 'const' instead.`,
14
+ });
15
+ });
16
+ }
17
+ };
18
+ }
19
+ };