@ripple-ts/eslint-plugin 0.2.153

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dominic Gannaway
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,266 @@
1
+ # @ripple-ts/eslint-plugin
2
+
3
+ ESLint plugin for [Ripple](https://ripple-ts.com) - helps enforce best practices and catch common mistakes when writing Ripple applications.
4
+
5
+ Works just like `eslint-plugin-react` - simply install and use the recommended config!
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install --save-dev '@ripple-ts/eslint-plugin'
11
+ # or
12
+ yarn add --dev '@ripple-ts/eslint-plugin'
13
+ # or
14
+ pnpm add --save-dev '@ripple-ts/eslint-plugin'
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Flat Config (ESLint 9+)
20
+
21
+ ```js
22
+ // eslint.config.js
23
+ import ripple from '@ripple-ts/eslint-plugin';
24
+
25
+ export default [...ripple.configs.recommended];
26
+ ```
27
+
28
+ The plugin automatically:
29
+
30
+ - Detects and uses `@ripple-ts/eslint-parser` if installed for `.ripple` files
31
+ - Detects and uses `@typescript-eslint/parser` if installed for `.ts`/`.tsx` files
32
+ - Excludes `.d.ts` files, `node_modules`, `dist`, and `build` directories from linting
33
+ - Works with both `.ts`/`.tsx` and `.ripple` files
34
+
35
+ ### Legacy Config (.eslintrc)
36
+
37
+ ```json
38
+ {
39
+ "plugins": ["ripple"],
40
+ "extends": ["plugin:ripple/recommended"]
41
+ }
42
+ ```
43
+
44
+ ## Configurations
45
+
46
+ ### `recommended`
47
+
48
+ The recommended configuration enables all rules at their default severity levels (errors and warnings).
49
+
50
+ ```js
51
+ import ripple from '@ripple-ts/eslint-plugin';
52
+
53
+ export default [
54
+ {
55
+ plugins: { ripple },
56
+ rules: ripple.configs.recommended.rules,
57
+ },
58
+ ];
59
+ ```
60
+
61
+ ### `strict`
62
+
63
+ The strict configuration enables all rules as errors.
64
+
65
+ ```js
66
+ import ripple from '@ripple-ts/eslint-plugin';
67
+
68
+ export default [
69
+ {
70
+ plugins: { ripple },
71
+ rules: ripple.configs.strict.rules,
72
+ },
73
+ ];
74
+ ```
75
+
76
+ ## Rules
77
+
78
+ ### `ripple/no-module-scope-track` (error)
79
+
80
+ Prevents calling `track()` at module scope. Tracked values must be created within a component context.
81
+
82
+ ❌ **Incorrect:**
83
+
84
+ ```js
85
+ import { track } from 'ripple';
86
+
87
+ // This will cause runtime errors
88
+ let globalCount = track(0);
89
+
90
+ export component App() {
91
+ <div>{@globalCount}</div>
92
+ }
93
+ ```
94
+
95
+ ✅ **Correct:**
96
+
97
+ ```js
98
+ import { track } from 'ripple';
99
+
100
+ export component App() {
101
+ // track() called within component
102
+ let count = track(0);
103
+
104
+ <div>{@count}</div>
105
+ }
106
+ ```
107
+
108
+ ### `ripple/require-component-export` (warning)
109
+
110
+ Warns when capitalized components are not exported. This helps ensure components are reusable across modules.
111
+
112
+ ❌ **Incorrect:**
113
+
114
+ ```js
115
+ component MyButton() {
116
+ <button>Click me</button>
117
+ }
118
+ // MyButton is defined but not exported
119
+ ```
120
+
121
+ ✅ **Correct:**
122
+
123
+ ```js
124
+ export component MyButton() {
125
+ <button>Click me</button>
126
+ }
127
+ ```
128
+
129
+ ### `ripple/prefer-oninput` (warning, fixable)
130
+
131
+ Recommends using `onInput` instead of `onChange` for form inputs. Unlike React, Ripple doesn't have synthetic events, so `onInput` is the correct event handler.
132
+
133
+ ❌ **Incorrect:**
134
+
135
+ ```jsx
136
+ <input onChange={handleChange} />
137
+ ```
138
+
139
+ ✅ **Correct:**
140
+
141
+ ```jsx
142
+ <input onInput={handleInput} />
143
+ ```
144
+
145
+ This rule is auto-fixable with `--fix`.
146
+
147
+ ### `ripple/no-return-in-component` (error)
148
+
149
+ Prevents returning JSX from Ripple components. In Ripple, JSX should be used as statements, not expressions.
150
+
151
+ ❌ **Incorrect:**
152
+
153
+ ```js
154
+ export component App() {
155
+ return <div>Hello World</div>;
156
+ }
157
+ ```
158
+
159
+ ✅ **Correct:**
160
+
161
+ ```js
162
+ export component App() {
163
+ <div>Hello World</div>
164
+ }
165
+ ```
166
+
167
+ ### `ripple/unbox-tracked-values` (error)
168
+
169
+ Ensures tracked values are unboxed with the `@` operator when used in JSX expressions.
170
+
171
+ ❌ **Incorrect:**
172
+
173
+ ```js
174
+ export component App() {
175
+ let count = track(0);
176
+
177
+ // Missing @ operator
178
+ <div>{count}</div>
179
+ }
180
+ ```
181
+
182
+ ✅ **Correct:**
183
+
184
+ ```js
185
+ export component App() {
186
+ let count = track(0);
187
+
188
+ // Properly unboxed with @
189
+ <div>{@count}</div>
190
+ }
191
+ ```
192
+
193
+ ### `ripple/no-introspect-in-modules` (error)
194
+
195
+ Prevents using the `@` introspection operator in TypeScript/JavaScript modules. In `.ts`/`.js` files, you should use `get()` and `set()` functions instead.
196
+
197
+ ❌ **Incorrect:**
198
+
199
+ ```ts
200
+ // count.ts
201
+ export function useCount() {
202
+ const count = track(1);
203
+ effect(() => {
204
+ console.log(@count); // Error: Cannot use @ in TypeScript modules
205
+ });
206
+ return { count };
207
+ }
208
+ ```
209
+
210
+ ✅ **Correct:**
211
+
212
+ ```ts
213
+ // count.ts
214
+ import { get, set } from 'ripple';
215
+
216
+ export function useCount() {
217
+ const count = track(1);
218
+
219
+ // Use get() to read tracked values
220
+ const double = derived(() => get(count) * 2);
221
+
222
+ effect(() => {
223
+ console.log('count is', get(count));
224
+ });
225
+
226
+ return { count, double };
227
+ }
228
+ ```
229
+
230
+ **Note:** The `@` operator is only valid in `.ripple` component files. In TypeScript modules, use `get()` to read values and `set()` to update them.
231
+
232
+ ## Custom Configuration
233
+
234
+ You can customize individual rules in your ESLint config:
235
+
236
+ ```js
237
+ export default [
238
+ {
239
+ plugins: { ripple },
240
+ rules: {
241
+ 'ripple/no-module-scope-track': 'error',
242
+ 'ripple/require-component-export': 'off', // Disable this rule
243
+ 'ripple/prefer-oninput': 'error', // Make this an error instead of warning
244
+ 'ripple/no-return-in-component': 'error',
245
+ 'ripple/unbox-tracked-values': 'error',
246
+ 'ripple/no-introspect-in-modules': 'error',
247
+ },
248
+ },
249
+ ];
250
+ ```
251
+
252
+ The plugin will automatically detect and use the Ripple parser for your `.ripple` files.
253
+
254
+ ## License
255
+
256
+ MIT
257
+
258
+ ## Contributing
259
+
260
+ Contributions are welcome! Please feel free to submit a Pull Request.
261
+
262
+ ## Related
263
+
264
+ - [Ripple](https://ripple-ts.com) - The Ripple framework
265
+ - [@ripple-ts/vite-plugin](https://www.npmjs.com/package/@ripple-ts/vite-plugin) - Vite plugin for Ripple
266
+ - [@ripple-ts/prettier-plugin](https://www.npmjs.com/package/@ripple-ts/prettier-plugin) - Prettier plugin for Ripple
@@ -0,0 +1,21 @@
1
+ import * as eslint0 from "eslint";
2
+
3
+ //#region src/index.d.ts
4
+ declare const plugin: {
5
+ meta: {
6
+ name: string;
7
+ version: string;
8
+ };
9
+ rules: {
10
+ 'no-module-scope-track': eslint0.Rule.RuleModule;
11
+ 'prefer-oninput': eslint0.Rule.RuleModule;
12
+ 'no-return-in-component': eslint0.Rule.RuleModule;
13
+ 'unbox-tracked-values': eslint0.Rule.RuleModule;
14
+ 'control-flow-jsx': eslint0.Rule.RuleModule;
15
+ 'no-introspect-in-modules': eslint0.Rule.RuleModule;
16
+ };
17
+ configs: any;
18
+ };
19
+ //#endregion
20
+ export { plugin as default };
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;cAQM;;;IAAA,OAcL,EAAA,MAAA;EAAA,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,355 @@
1
+ import { createRequire } from "module";
2
+
3
+ //#region src/rules/no-module-scope-track.ts
4
+ const rule$5 = {
5
+ meta: {
6
+ type: "problem",
7
+ docs: {
8
+ description: "Disallow calling track() at module scope",
9
+ category: "Possible Errors",
10
+ recommended: true
11
+ },
12
+ messages: { moduleScope: "track() cannot be called at module scope. It must be called within a component context." },
13
+ schema: []
14
+ },
15
+ create(context) {
16
+ let componentDepth = 0;
17
+ let functionDepth = 0;
18
+ const incrementComponentDepth = () => componentDepth++;
19
+ const decrementComponentDepth = () => componentDepth--;
20
+ const incrementFunctionDepth = () => functionDepth++;
21
+ const decrementFunctionDepth = () => functionDepth--;
22
+ return {
23
+ "Component": incrementComponentDepth,
24
+ "Component:exit": decrementComponentDepth,
25
+ "FunctionDeclaration": incrementFunctionDepth,
26
+ "FunctionDeclaration:exit": decrementFunctionDepth,
27
+ "FunctionExpression": incrementFunctionDepth,
28
+ "FunctionExpression:exit": decrementFunctionDepth,
29
+ "ArrowFunctionExpression": incrementFunctionDepth,
30
+ "ArrowFunctionExpression:exit": decrementFunctionDepth,
31
+ CallExpression(node) {
32
+ if (node.callee.type === "Identifier" && node.callee.name === "track" && componentDepth === 0 && functionDepth === 0) context.report({
33
+ node,
34
+ messageId: "moduleScope"
35
+ });
36
+ }
37
+ };
38
+ }
39
+ };
40
+ var no_module_scope_track_default = rule$5;
41
+
42
+ //#endregion
43
+ //#region src/rules/prefer-oninput.ts
44
+ const rule$4 = {
45
+ meta: {
46
+ type: "suggestion",
47
+ docs: {
48
+ description: "Prefer onInput over onChange for form inputs in Ripple",
49
+ category: "Best Practices",
50
+ recommended: true
51
+ },
52
+ messages: { preferOnInput: "Use \"onInput\" instead of \"onChange\". Ripple does not have synthetic events like React." },
53
+ fixable: "code",
54
+ schema: []
55
+ },
56
+ create(context) {
57
+ return {
58
+ "JSXAttribute[name.name=\"onChange\"]"(node) {
59
+ context.report({
60
+ node,
61
+ messageId: "preferOnInput",
62
+ fix(fixer) {
63
+ return fixer.replaceText(node.name, "onInput");
64
+ }
65
+ });
66
+ },
67
+ "Attribute[name.name=\"onChange\"]"(node) {
68
+ context.report({
69
+ node,
70
+ messageId: "preferOnInput",
71
+ fix(fixer) {
72
+ return fixer.replaceText(node.name, "onInput");
73
+ }
74
+ });
75
+ },
76
+ "Property[key.name=\"onChange\"]"(node) {
77
+ if (context.sourceCode.getAncestors(node).some((ancestor) => ancestor.type === "ObjectExpression")) context.report({
78
+ node,
79
+ messageId: "preferOnInput",
80
+ fix(fixer) {
81
+ return fixer.replaceText(node.key, "onInput");
82
+ }
83
+ });
84
+ }
85
+ };
86
+ }
87
+ };
88
+ var prefer_oninput_default = rule$4;
89
+
90
+ //#endregion
91
+ //#region src/rules/no-return-in-component.ts
92
+ const rule$3 = {
93
+ meta: {
94
+ type: "problem",
95
+ docs: {
96
+ description: "Disallow return statements with JSX in Ripple components",
97
+ category: "Possible Errors",
98
+ recommended: true
99
+ },
100
+ messages: { noReturn: "Do not return JSX from Ripple components. Use JSX as statements instead." },
101
+ schema: []
102
+ },
103
+ create(context) {
104
+ let insideComponent = 0;
105
+ return {
106
+ "ExpressionStatement > CallExpression[callee.name='component']"() {
107
+ insideComponent++;
108
+ },
109
+ "ExpressionStatement > CallExpression[callee.name='component']:exit"() {
110
+ insideComponent--;
111
+ },
112
+ "VariableDeclarator[init.callee.name=\"component\"]"() {
113
+ insideComponent++;
114
+ },
115
+ "VariableDeclarator[init.callee.name=\"component\"]:exit"() {
116
+ insideComponent--;
117
+ },
118
+ ReturnStatement(node) {
119
+ if (insideComponent > 0 && node.argument) {
120
+ if (node.argument.type === "JSXElement" || node.argument.type === "JSXFragment") context.report({
121
+ node,
122
+ messageId: "noReturn"
123
+ });
124
+ }
125
+ }
126
+ };
127
+ }
128
+ };
129
+ var no_return_in_component_default = rule$3;
130
+
131
+ //#endregion
132
+ //#region src/rules/unbox-tracked-values.ts
133
+ const rule$2 = {
134
+ meta: {
135
+ type: "problem",
136
+ docs: {
137
+ description: "Ensure tracked values are unboxed with @ operator",
138
+ category: "Possible Errors",
139
+ recommended: true
140
+ },
141
+ messages: { needsUnbox: "Tracked value should be unboxed with @ operator. Did you mean \"@{{name}}\"?" },
142
+ schema: []
143
+ },
144
+ create(context) {
145
+ const trackedVariables = /* @__PURE__ */ new Set();
146
+ function isInJSXContext(node) {
147
+ let parent = node.parent;
148
+ while (parent) {
149
+ const parentType = parent.type;
150
+ if (parentType === "JSXExpressionContainer" || parentType === "JSXElement" || parentType === "JSXFragment" || parentType === "ExpressionContainer" || parentType === "Element") return true;
151
+ parent = parent.parent;
152
+ }
153
+ return false;
154
+ }
155
+ function checkTrackedIdentifier(node) {
156
+ if (trackedVariables.has(node.name) && isInJSXContext(node)) {
157
+ const parent = node.parent;
158
+ let isUnboxed = parent && parent.type === "UnaryExpression" && parent.operator === "@";
159
+ if (!isUnboxed) isUnboxed = context.getSourceCode().text.substring(Math.max(0, node.range[0] - 1), node.range[0]) === "@";
160
+ if (!isUnboxed) context.report({
161
+ node,
162
+ messageId: "needsUnbox",
163
+ data: { name: node.name }
164
+ });
165
+ }
166
+ }
167
+ return {
168
+ "VariableDeclarator[init.callee.name=\"track\"]"(node) {
169
+ if (node.id.type === "Identifier") trackedVariables.add(node.id.name);
170
+ },
171
+ Identifier(node) {
172
+ checkTrackedIdentifier(node);
173
+ }
174
+ };
175
+ }
176
+ };
177
+ var unbox_tracked_values_default = rule$2;
178
+
179
+ //#endregion
180
+ //#region src/rules/control-flow-jsx.ts
181
+ const rule$1 = {
182
+ meta: {
183
+ type: "problem",
184
+ docs: {
185
+ description: "Require JSX in for...of loops within components, but disallow JSX in for...of loops within effects",
186
+ category: "Possible Errors",
187
+ recommended: true
188
+ },
189
+ messages: {
190
+ requireJsxInLoop: "For...of loops in component bodies should contain JSX elements. Use JSX to render items.",
191
+ noJsxInEffectLoop: "For...of loops inside effect() should not contain JSX. Effects are for side effects, not rendering."
192
+ },
193
+ schema: []
194
+ },
195
+ create(context) {
196
+ let insideComponent = 0;
197
+ let insideEffect = 0;
198
+ function containsJSX(node, visited = /* @__PURE__ */ new Set()) {
199
+ if (!node) return false;
200
+ if (visited.has(node)) return false;
201
+ visited.add(node);
202
+ if (node.type === "JSXElement" || node.type === "JSXFragment" || node.type === "Element") return true;
203
+ const keys = Object.keys(node);
204
+ for (const key of keys) {
205
+ if (key === "parent" || key === "loc" || key === "range") continue;
206
+ const value = node[key];
207
+ if (value && typeof value === "object") {
208
+ if (Array.isArray(value)) {
209
+ for (const item of value) if (item && typeof item === "object" && containsJSX(item, visited)) return true;
210
+ } else if (value.type && containsJSX(value, visited)) return true;
211
+ }
212
+ }
213
+ return false;
214
+ }
215
+ return {
216
+ Component() {
217
+ insideComponent++;
218
+ },
219
+ "Component:exit"() {
220
+ insideComponent--;
221
+ },
222
+ "CallExpression[callee.name='effect']"() {
223
+ insideEffect++;
224
+ },
225
+ "CallExpression[callee.name='effect']:exit"() {
226
+ insideEffect--;
227
+ },
228
+ ForOfStatement(node) {
229
+ if (insideComponent === 0) return;
230
+ const hasJSX = containsJSX(node.body);
231
+ if (insideEffect > 0) {
232
+ if (hasJSX) context.report({
233
+ node,
234
+ messageId: "noJsxInEffectLoop"
235
+ });
236
+ } else if (!hasJSX) context.report({
237
+ node,
238
+ messageId: "requireJsxInLoop"
239
+ });
240
+ }
241
+ };
242
+ }
243
+ };
244
+ var control_flow_jsx_default = rule$1;
245
+
246
+ //#endregion
247
+ //#region src/rules/no-introspect-in-modules.ts
248
+ const rule = {
249
+ meta: {
250
+ type: "problem",
251
+ docs: {
252
+ description: "Disallow @ introspection operator in TypeScript/JavaScript modules",
253
+ category: "Possible Errors",
254
+ recommended: true
255
+ },
256
+ messages: { noIntrospect: "The @ operator cannot be used in TypeScript/JavaScript modules. Use get() to read tracked values and set() to update them instead." },
257
+ schema: []
258
+ },
259
+ create(context) {
260
+ const filename = context.filename || context.getFilename();
261
+ if (filename && filename.endsWith(".ripple")) return {};
262
+ return { Identifier(node) {
263
+ if (node.tracked === true) context.report({
264
+ node,
265
+ messageId: "noIntrospect"
266
+ });
267
+ } };
268
+ }
269
+ };
270
+ var no_introspect_in_modules_default = rule;
271
+
272
+ //#endregion
273
+ //#region src/index.ts
274
+ const plugin = {
275
+ meta: {
276
+ name: "@ripple-ts/eslint-plugin",
277
+ version: "0.1.3"
278
+ },
279
+ rules: {
280
+ "no-module-scope-track": no_module_scope_track_default,
281
+ "prefer-oninput": prefer_oninput_default,
282
+ "no-return-in-component": no_return_in_component_default,
283
+ "unbox-tracked-values": unbox_tracked_values_default,
284
+ "control-flow-jsx": control_flow_jsx_default,
285
+ "no-introspect-in-modules": no_introspect_in_modules_default
286
+ },
287
+ configs: {}
288
+ };
289
+ const require = createRequire(import.meta.url);
290
+ let rippleParser;
291
+ let tsParser;
292
+ try {
293
+ rippleParser = require("@ripple-ts/eslint-parser");
294
+ } catch {
295
+ rippleParser = null;
296
+ }
297
+ try {
298
+ tsParser = require("@typescript-eslint/parser");
299
+ } catch {
300
+ tsParser = null;
301
+ }
302
+ function createConfig(name, files, parser) {
303
+ const config = {
304
+ name,
305
+ files,
306
+ plugins: { ripple: plugin },
307
+ rules: {
308
+ "ripple/no-module-scope-track": "error",
309
+ "ripple/prefer-oninput": "warn",
310
+ "ripple/no-return-in-component": "error",
311
+ "ripple/unbox-tracked-values": "error",
312
+ "ripple/control-flow-jsx": "error",
313
+ "ripple/no-introspect-in-modules": "error"
314
+ }
315
+ };
316
+ if (parser) config.languageOptions = {
317
+ parser,
318
+ parserOptions: {
319
+ ecmaVersion: "latest",
320
+ sourceType: "module"
321
+ }
322
+ };
323
+ return config;
324
+ }
325
+ plugin.configs.recommended = [
326
+ createConfig("ripple/recommended-ripple-files", ["**/*.ripple"], rippleParser),
327
+ createConfig("ripple/recommended-typescript-files", ["**/*.ts", "**/*.tsx"], tsParser),
328
+ {
329
+ name: "ripple/ignores",
330
+ ignores: [
331
+ "**/*.d.ts",
332
+ "**/node_modules/**",
333
+ "**/dist/**",
334
+ "**/build/**"
335
+ ]
336
+ }
337
+ ];
338
+ plugin.configs.strict = [
339
+ createConfig("ripple/strict-ripple-files", ["**/*.ripple"], rippleParser),
340
+ createConfig("ripple/strict-typescript-files", ["**/*.ts", "**/*.tsx"], tsParser),
341
+ {
342
+ name: "ripple/ignores",
343
+ ignores: [
344
+ "**/*.d.ts",
345
+ "**/node_modules/**",
346
+ "**/dist/**",
347
+ "**/build/**"
348
+ ]
349
+ }
350
+ ];
351
+ var src_default = plugin;
352
+
353
+ //#endregion
354
+ export { src_default as default };
355
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["rule: Rule.RuleModule","rule","rule: Rule.RuleModule","rule","rule: Rule.RuleModule","rule","rule: Rule.RuleModule","rule","rule: Rule.RuleModule","rule","rule: Rule.RuleModule","noModuleScopeTrack","preferOnInput","noReturnInComponent","unboxTrackedValues","controlFlowJsx","noIntrospectInModules","rippleParser: any","tsParser: any","config: any"],"sources":["../src/rules/no-module-scope-track.ts","../src/rules/prefer-oninput.ts","../src/rules/no-return-in-component.ts","../src/rules/unbox-tracked-values.ts","../src/rules/control-flow-jsx.ts","../src/rules/no-introspect-in-modules.ts","../src/index.ts"],"sourcesContent":["import type { Rule } from 'eslint';\nimport type { CallExpression } from 'estree';\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow calling track() at module scope',\n category: 'Possible Errors',\n recommended: true,\n },\n messages: {\n moduleScope: 'track() cannot be called at module scope. It must be called within a component context.',\n },\n schema: [],\n },\n create(context) {\n let componentDepth = 0;\n let functionDepth = 0;\n\n const incrementComponentDepth = () => componentDepth++;\n const decrementComponentDepth = () => componentDepth--;\n const incrementFunctionDepth = () => functionDepth++;\n const decrementFunctionDepth = () => functionDepth--;\n\n return {\n // Only track when we enter a Ripple component\n // Ripple's parser returns \"Component\" nodes for component declarations\n 'Component': incrementComponentDepth,\n 'Component:exit': decrementComponentDepth,\n\n // Track regular functions and arrow functions\n 'FunctionDeclaration': incrementFunctionDepth,\n 'FunctionDeclaration:exit': decrementFunctionDepth,\n 'FunctionExpression': incrementFunctionDepth,\n 'FunctionExpression:exit': decrementFunctionDepth,\n 'ArrowFunctionExpression': incrementFunctionDepth,\n 'ArrowFunctionExpression:exit': decrementFunctionDepth,\n\n // Check track() calls\n CallExpression(node: CallExpression) {\n if (\n node.callee.type === 'Identifier' &&\n node.callee.name === 'track' &&\n componentDepth === 0 &&\n functionDepth === 0\n ) {\n context.report({\n node,\n messageId: 'moduleScope',\n });\n }\n },\n };\n },\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Prefer onInput over onChange for form inputs in Ripple',\n category: 'Best Practices',\n recommended: true,\n },\n messages: {\n preferOnInput: 'Use \"onInput\" instead of \"onChange\". Ripple does not have synthetic events like React.',\n },\n fixable: 'code',\n schema: [],\n },\n create(context) {\n return {\n // Check JSX attributes (standard JSX)\n 'JSXAttribute[name.name=\"onChange\"]'(node: any) {\n context.report({\n node,\n messageId: 'preferOnInput',\n fix(fixer) {\n return fixer.replaceText(node.name, 'onInput');\n },\n });\n },\n // Check Attribute nodes (Ripple parser)\n 'Attribute[name.name=\"onChange\"]'(node: any) {\n context.report({\n node,\n messageId: 'preferOnInput',\n fix(fixer) {\n return fixer.replaceText(node.name, 'onInput');\n },\n });\n },\n // Check object properties (for spread props)\n 'Property[key.name=\"onChange\"]'(node: any) {\n // Only report if this looks like it's in a props object\n const ancestors = context.sourceCode.getAncestors(node);\n const inObjectExpression = ancestors.some(\n (ancestor) => ancestor.type === 'ObjectExpression'\n );\n\n if (inObjectExpression) {\n context.report({\n node,\n messageId: 'preferOnInput',\n fix(fixer) {\n return fixer.replaceText(node.key, 'onInput');\n },\n });\n }\n },\n };\n },\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport type { ReturnStatement } from 'estree';\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow return statements with JSX in Ripple components',\n category: 'Possible Errors',\n recommended: true,\n },\n messages: {\n noReturn: 'Do not return JSX from Ripple components. Use JSX as statements instead.',\n },\n schema: [],\n },\n create(context) {\n let insideComponent = 0;\n\n return {\n // Track component boundaries\n \"ExpressionStatement > CallExpression[callee.name='component']\"() {\n insideComponent++;\n },\n \"ExpressionStatement > CallExpression[callee.name='component']:exit\"() {\n insideComponent--;\n },\n // Also track arrow functions and regular functions that might be components\n 'VariableDeclarator[init.callee.name=\"component\"]'() {\n insideComponent++;\n },\n 'VariableDeclarator[init.callee.name=\"component\"]:exit'() {\n insideComponent--;\n },\n // Check return statements\n ReturnStatement(node: ReturnStatement) {\n if (insideComponent > 0 && node.argument) {\n // Check if returning JSX (JSXElement, JSXFragment)\n if (\n node.argument.type === 'JSXElement' ||\n node.argument.type === 'JSXFragment'\n ) {\n context.report({\n node,\n messageId: 'noReturn',\n });\n }\n }\n },\n };\n },\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Ensure tracked values are unboxed with @ operator',\n category: 'Possible Errors',\n recommended: true,\n },\n messages: {\n needsUnbox: 'Tracked value should be unboxed with @ operator. Did you mean \"@{{name}}\"?',\n },\n schema: [],\n },\n create(context) {\n const trackedVariables = new Set<string>();\n\n function isInJSXContext(node: any): boolean {\n let parent = node.parent;\n\n // Walk up the AST to find if we're inside JSX/Element\n while (parent) {\n const parentType = parent.type;\n // Check for JSX context\n if (\n parentType === 'JSXExpressionContainer' ||\n parentType === 'JSXElement' ||\n parentType === 'JSXFragment' ||\n // Check for Ripple Element context\n parentType === 'ExpressionContainer' ||\n parentType === 'Element'\n ) {\n return true;\n }\n parent = parent.parent;\n }\n\n return false;\n }\n\n function checkTrackedIdentifier(node: any) {\n if (trackedVariables.has(node.name) && isInJSXContext(node)) {\n // Check if it's not already unboxed (preceded by @)\n // The @ operator in Ripple creates a UnaryExpression node\n const parent = node.parent;\n let isUnboxed = parent &&\n parent.type === 'UnaryExpression' &&\n parent.operator === '@';\n\n // Fallback: check source code for @ character before the identifier\n if (!isUnboxed) {\n const sourceCode = context.getSourceCode();\n const textBefore = sourceCode.text.substring(\n Math.max(0, node.range![0] - 1),\n node.range![0]\n );\n isUnboxed = textBefore === '@';\n }\n\n if (!isUnboxed) {\n context.report({\n node,\n messageId: 'needsUnbox',\n data: { name: node.name },\n });\n }\n }\n }\n\n return {\n // Track variables that are assigned from track()\n 'VariableDeclarator[init.callee.name=\"track\"]'(node: any) {\n if (node.id.type === 'Identifier') {\n trackedVariables.add(node.id.name);\n }\n },\n // Check all identifiers\n Identifier(node: any) {\n checkTrackedIdentifier(node);\n },\n };\n },\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport type { ForOfStatement, Node } from 'estree';\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require JSX in for...of loops within components, but disallow JSX in for...of loops within effects',\n category: 'Possible Errors',\n recommended: true,\n },\n messages: {\n requireJsxInLoop:\n 'For...of loops in component bodies should contain JSX elements. Use JSX to render items.',\n noJsxInEffectLoop:\n 'For...of loops inside effect() should not contain JSX. Effects are for side effects, not rendering.',\n },\n schema: [],\n },\n create(context) {\n let insideComponent = 0;\n let insideEffect = 0;\n\n function containsJSX(node: Node, visited: Set<Node> = new Set()): boolean {\n if (!node) return false;\n\n // Avoid infinite loops from circular references\n if (visited.has(node)) return false;\n visited.add(node);\n\n // Check if current node is JSX/Element (Ripple uses 'Element' type instead of 'JSXElement')\n if (node.type === 'JSXElement' as string || node.type === 'JSXFragment' as string || node.type === 'Element' as string) {\n return true;\n }\n\n const keys = Object.keys(node);\n for (const key of keys) {\n if (key === 'parent' || key === 'loc' || key === 'range') {\n continue;\n }\n\n const value = (node as any)[key];\n if (value && typeof value === 'object') {\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item && typeof item === 'object' && containsJSX(item, visited)) {\n return true;\n }\n }\n } else if (value.type && containsJSX(value, visited)) {\n return true;\n }\n }\n }\n\n return false;\n }\n\n return {\n Component() {\n insideComponent++;\n },\n 'Component:exit'() {\n insideComponent--;\n },\n\n \"CallExpression[callee.name='effect']\"() {\n insideEffect++;\n },\n \"CallExpression[callee.name='effect']:exit\"() {\n insideEffect--;\n },\n\n ForOfStatement(node: ForOfStatement) {\n if (insideComponent === 0) return;\n\n const hasJSX = containsJSX(node.body);\n\n if (insideEffect > 0) {\n if (hasJSX) {\n context.report({\n node,\n messageId: 'noJsxInEffectLoop',\n });\n }\n } else {\n if (!hasJSX) {\n context.report({\n node,\n messageId: 'requireJsxInLoop',\n });\n }\n }\n },\n };\n },\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow @ introspection operator in TypeScript/JavaScript modules',\n category: 'Possible Errors',\n recommended: true,\n },\n messages: {\n noIntrospect: 'The @ operator cannot be used in TypeScript/JavaScript modules. Use get() to read tracked values and set() to update them instead.',\n },\n schema: [],\n },\n create(context) {\n const filename = context.filename || context.getFilename();\n \n // Skip .ripple files where @ operator is valid\n if (filename && filename.endsWith('.ripple')) {\n return {};\n }\n\n return {\n // Check for identifiers with the 'tracked' property\n // The @ operator is parsed by Ripple as an identifier with tracked=true\n Identifier(node: any) {\n if (node.tracked === true) {\n context.report({\n node,\n messageId: 'noIntrospect',\n });\n }\n },\n };\n },\n};\n\nexport default rule;\n\n","import { createRequire } from 'module';\nimport noModuleScopeTrack from './rules/no-module-scope-track.js';\nimport preferOnInput from './rules/prefer-oninput.js';\nimport noReturnInComponent from './rules/no-return-in-component.js';\nimport unboxTrackedValues from './rules/unbox-tracked-values.js';\nimport controlFlowJsx from './rules/control-flow-jsx.js';\nimport noIntrospectInModules from './rules/no-introspect-in-modules.js';\n\nconst plugin = {\n\tmeta: {\n\t\tname: '@ripple-ts/eslint-plugin',\n\t\tversion: '0.1.3',\n\t},\n\trules: {\n\t\t'no-module-scope-track': noModuleScopeTrack,\n\t\t'prefer-oninput': preferOnInput,\n\t\t'no-return-in-component': noReturnInComponent,\n\t\t'unbox-tracked-values': unboxTrackedValues,\n\t\t'control-flow-jsx': controlFlowJsx,\n\t\t'no-introspect-in-modules': noIntrospectInModules,\n\t},\n\tconfigs: {} as any,\n};\n\n// Try to load optional parsers\nconst require = createRequire(import.meta.url);\n\nlet rippleParser: any;\nlet tsParser: any;\n\ntry {\n\trippleParser = require('@ripple-ts/eslint-parser');\n} catch {\n\t// @ripple-ts/eslint-parser is optional\n\trippleParser = null;\n}\n\ntry {\n\ttsParser = require('@typescript-eslint/parser');\n} catch {\n\t// @typescript-eslint/parser is optional\n\ttsParser = null;\n}\n\n// Helper to create config objects\nfunction createConfig(name: string, files: string[], parser: any) {\n\tconst config: any = {\n\t\tname,\n\t\tfiles,\n\t\tplugins: {\n\t\t\tripple: plugin,\n\t\t},\n\t\trules: {\n\t\t\t'ripple/no-module-scope-track': 'error',\n\t\t\t'ripple/prefer-oninput': 'warn',\n\t\t\t'ripple/no-return-in-component': 'error',\n\t\t\t'ripple/unbox-tracked-values': 'error',\n\t\t\t'ripple/control-flow-jsx': 'error',\n\t\t\t'ripple/no-introspect-in-modules': 'error',\n\t\t},\n\t};\n\n\t// Only add parser if it's available\n\tif (parser) {\n\t\tconfig.languageOptions = {\n\t\t\tparser,\n\t\t\tparserOptions: {\n\t\t\t\tecmaVersion: 'latest',\n\t\t\t\tsourceType: 'module',\n\t\t\t},\n\t\t};\n\t}\n\n\treturn config;\n}\n\n// Recommended configuration (flat config format)\nplugin.configs.recommended = [\n\tcreateConfig('ripple/recommended-ripple-files', ['**/*.ripple'], rippleParser),\n\tcreateConfig('ripple/recommended-typescript-files', ['**/*.ts', '**/*.tsx'], tsParser),\n\t{\n\t\tname: 'ripple/ignores',\n\t\tignores: ['**/*.d.ts', '**/node_modules/**', '**/dist/**', '**/build/**'],\n\t},\n];\n\n// Strict configuration (flat config format)\nplugin.configs.strict = [\n\tcreateConfig('ripple/strict-ripple-files', ['**/*.ripple'], rippleParser),\n\tcreateConfig('ripple/strict-typescript-files', ['**/*.ts', '**/*.tsx'], tsParser),\n\t{\n\t\tname: 'ripple/ignores',\n\t\tignores: ['**/*.d.ts', '**/node_modules/**', '**/dist/**', '**/build/**'],\n\t},\n];\n\nexport default plugin;\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@ripple-ts/eslint-plugin",
3
+ "version": "0.2.153",
4
+ "description": "ESLint plugin for Ripple",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "exports": {
8
+ ".": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "keywords": [
14
+ "eslint",
15
+ "eslintplugin",
16
+ "eslint-plugin",
17
+ "ripple"
18
+ ],
19
+ "author": "Dominic Gannaway",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/Ripple-TS/ripple.git",
24
+ "directory": "packages/eslint-plugin"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/Ripple-TS/ripple/issues"
28
+ },
29
+ "homepage": "https://ripple-ts.com",
30
+ "peerDependencies": {
31
+ "eslint": ">=9.0.0",
32
+ "@typescript-eslint/parser": "^8.20.0",
33
+ "@ripple-ts/eslint-parser": "0.2.153"
34
+ },
35
+ "peerDependenciesMeta": {
36
+ "@typescript-eslint/parser": {
37
+ "optional": true
38
+ },
39
+ "@ripple-ts/eslint-parser": {
40
+ "optional": true
41
+ }
42
+ },
43
+ "devDependencies": {
44
+ "@types/eslint": "^9.6.1",
45
+ "@types/estree": "^1.0.8",
46
+ "@types/node": "^24.3.0",
47
+ "@typescript-eslint/parser": "^8.20.0",
48
+ "eslint": "^9.0.0",
49
+ "tsdown": "^0.15.4",
50
+ "typescript": "^5.9.2",
51
+ "vitest": "^3.2.4",
52
+ "@ripple-ts/eslint-parser": "0.2.153"
53
+ },
54
+ "engines": {
55
+ "node": ">=20.0.0"
56
+ },
57
+ "scripts": {
58
+ "dist": "perl -pi -e 's/\"main\": \"src\\/index.ts\"/\"main\": \"dist\\/index.js\"/' package.json",
59
+ "src": "perl -pi -e 's/\"main\": \"dist\\/index.js\"/\"main\": \"src\\/index.ts\"/' package.json",
60
+ "build": "pnpm dist && tsdown && pnpm src || pnpm src",
61
+ "test": "vitest run",
62
+ "test:watch": "vitest"
63
+ }
64
+ }
package/src/index.ts ADDED
@@ -0,0 +1,97 @@
1
+ import { createRequire } from 'module';
2
+ import noModuleScopeTrack from './rules/no-module-scope-track.js';
3
+ import preferOnInput from './rules/prefer-oninput.js';
4
+ import noReturnInComponent from './rules/no-return-in-component.js';
5
+ import unboxTrackedValues from './rules/unbox-tracked-values.js';
6
+ import controlFlowJsx from './rules/control-flow-jsx.js';
7
+ import noIntrospectInModules from './rules/no-introspect-in-modules.js';
8
+
9
+ const plugin = {
10
+ meta: {
11
+ name: '@ripple-ts/eslint-plugin',
12
+ version: '0.1.3',
13
+ },
14
+ rules: {
15
+ 'no-module-scope-track': noModuleScopeTrack,
16
+ 'prefer-oninput': preferOnInput,
17
+ 'no-return-in-component': noReturnInComponent,
18
+ 'unbox-tracked-values': unboxTrackedValues,
19
+ 'control-flow-jsx': controlFlowJsx,
20
+ 'no-introspect-in-modules': noIntrospectInModules,
21
+ },
22
+ configs: {} as any,
23
+ };
24
+
25
+ // Try to load optional parsers
26
+ const require = createRequire(import.meta.url);
27
+
28
+ let rippleParser: any;
29
+ let tsParser: any;
30
+
31
+ try {
32
+ rippleParser = require('@ripple-ts/eslint-parser');
33
+ } catch {
34
+ // @ripple-ts/eslint-parser is optional
35
+ rippleParser = null;
36
+ }
37
+
38
+ try {
39
+ tsParser = require('@typescript-eslint/parser');
40
+ } catch {
41
+ // @typescript-eslint/parser is optional
42
+ tsParser = null;
43
+ }
44
+
45
+ // Helper to create config objects
46
+ function createConfig(name: string, files: string[], parser: any) {
47
+ const config: any = {
48
+ name,
49
+ files,
50
+ plugins: {
51
+ ripple: plugin,
52
+ },
53
+ rules: {
54
+ 'ripple/no-module-scope-track': 'error',
55
+ 'ripple/prefer-oninput': 'warn',
56
+ 'ripple/no-return-in-component': 'error',
57
+ 'ripple/unbox-tracked-values': 'error',
58
+ 'ripple/control-flow-jsx': 'error',
59
+ 'ripple/no-introspect-in-modules': 'error',
60
+ },
61
+ };
62
+
63
+ // Only add parser if it's available
64
+ if (parser) {
65
+ config.languageOptions = {
66
+ parser,
67
+ parserOptions: {
68
+ ecmaVersion: 'latest',
69
+ sourceType: 'module',
70
+ },
71
+ };
72
+ }
73
+
74
+ return config;
75
+ }
76
+
77
+ // Recommended configuration (flat config format)
78
+ plugin.configs.recommended = [
79
+ createConfig('ripple/recommended-ripple-files', ['**/*.ripple'], rippleParser),
80
+ createConfig('ripple/recommended-typescript-files', ['**/*.ts', '**/*.tsx'], tsParser),
81
+ {
82
+ name: 'ripple/ignores',
83
+ ignores: ['**/*.d.ts', '**/node_modules/**', '**/dist/**', '**/build/**'],
84
+ },
85
+ ];
86
+
87
+ // Strict configuration (flat config format)
88
+ plugin.configs.strict = [
89
+ createConfig('ripple/strict-ripple-files', ['**/*.ripple'], rippleParser),
90
+ createConfig('ripple/strict-typescript-files', ['**/*.ts', '**/*.tsx'], tsParser),
91
+ {
92
+ name: 'ripple/ignores',
93
+ ignores: ['**/*.d.ts', '**/node_modules/**', '**/dist/**', '**/build/**'],
94
+ },
95
+ ];
96
+
97
+ export default plugin;