@codyswann/lisa 2.178.6 → 2.179.0

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 (102) hide show
  1. package/dist/configs/eslint/phaser.d.ts +2 -8
  2. package/dist/configs/eslint/phaser.d.ts.map +1 -1
  3. package/dist/configs/eslint/phaser.js +143 -17
  4. package/dist/configs/eslint/phaser.js.map +1 -1
  5. package/eslint-plugin-phaser/README.md +19 -0
  6. package/eslint-plugin-phaser/index.js +35 -0
  7. package/eslint-plugin-phaser/package.json +10 -0
  8. package/eslint-plugin-phaser/rules/no-allocation-in-update.js +189 -0
  9. package/eslint-plugin-phaser/rules/no-create-in-update.js +200 -0
  10. package/eslint-plugin-phaser/rules/require-shutdown-cleanup.js +191 -0
  11. package/package.json +5 -3
  12. package/phaser/package-lisa/package.lisa.json +11 -3
  13. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  14. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  15. package/plugins/lisa-agy/plugin.json +1 -1
  16. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  19. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  25. package/plugins/lisa-expo-agy/plugin.json +1 -1
  26. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  30. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  31. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  35. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  36. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  40. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  41. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  45. package/plugins/lisa-phaser/rules/phaser.md +111 -44
  46. package/plugins/lisa-phaser/skills/phaser-accessibility/SKILL.md +135 -0
  47. package/plugins/lisa-phaser/skills/phaser-accessibility/agents/openai.yaml +4 -0
  48. package/plugins/lisa-phaser/skills/phaser-asset-pipeline/SKILL.md +148 -0
  49. package/plugins/lisa-phaser/skills/phaser-asset-pipeline/agents/openai.yaml +4 -0
  50. package/plugins/lisa-phaser/skills/phaser-build-deploy/SKILL.md +148 -0
  51. package/plugins/lisa-phaser/skills/phaser-build-deploy/agents/openai.yaml +4 -0
  52. package/plugins/lisa-phaser/skills/phaser-i18n/SKILL.md +122 -0
  53. package/plugins/lisa-phaser/skills/phaser-i18n/agents/openai.yaml +4 -0
  54. package/plugins/lisa-phaser/skills/phaser-services/SKILL.md +208 -0
  55. package/plugins/lisa-phaser/skills/phaser-services/agents/openai.yaml +4 -0
  56. package/plugins/lisa-phaser/skills/phaser-testing/SKILL.md +103 -10
  57. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  58. package/plugins/lisa-phaser-agy/skills/phaser-accessibility/SKILL.md +135 -0
  59. package/plugins/lisa-phaser-agy/skills/phaser-asset-pipeline/SKILL.md +148 -0
  60. package/plugins/lisa-phaser-agy/skills/phaser-build-deploy/SKILL.md +148 -0
  61. package/plugins/lisa-phaser-agy/skills/phaser-i18n/SKILL.md +122 -0
  62. package/plugins/lisa-phaser-agy/skills/phaser-services/SKILL.md +208 -0
  63. package/plugins/lisa-phaser-agy/skills/phaser-testing/SKILL.md +103 -10
  64. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-phaser-copilot/rules/phaser.md +111 -44
  66. package/plugins/lisa-phaser-copilot/skills/phaser-accessibility/SKILL.md +135 -0
  67. package/plugins/lisa-phaser-copilot/skills/phaser-asset-pipeline/SKILL.md +148 -0
  68. package/plugins/lisa-phaser-copilot/skills/phaser-build-deploy/SKILL.md +148 -0
  69. package/plugins/lisa-phaser-copilot/skills/phaser-i18n/SKILL.md +122 -0
  70. package/plugins/lisa-phaser-copilot/skills/phaser-services/SKILL.md +208 -0
  71. package/plugins/lisa-phaser-copilot/skills/phaser-testing/SKILL.md +103 -10
  72. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-phaser-cursor/rules/phaser.mdc +111 -44
  74. package/plugins/lisa-phaser-cursor/skills/phaser-accessibility/SKILL.md +135 -0
  75. package/plugins/lisa-phaser-cursor/skills/phaser-asset-pipeline/SKILL.md +148 -0
  76. package/plugins/lisa-phaser-cursor/skills/phaser-build-deploy/SKILL.md +148 -0
  77. package/plugins/lisa-phaser-cursor/skills/phaser-i18n/SKILL.md +122 -0
  78. package/plugins/lisa-phaser-cursor/skills/phaser-services/SKILL.md +208 -0
  79. package/plugins/lisa-phaser-cursor/skills/phaser-testing/SKILL.md +103 -10
  80. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  82. package/plugins/lisa-rails-agy/plugin.json +1 -1
  83. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  87. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  88. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  90. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  92. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  93. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  95. package/plugins/src/phaser/rules/phaser.md +111 -44
  96. package/plugins/src/phaser/skills/phaser-accessibility/SKILL.md +135 -0
  97. package/plugins/src/phaser/skills/phaser-asset-pipeline/SKILL.md +148 -0
  98. package/plugins/src/phaser/skills/phaser-build-deploy/SKILL.md +148 -0
  99. package/plugins/src/phaser/skills/phaser-i18n/SKILL.md +122 -0
  100. package/plugins/src/phaser/skills/phaser-services/SKILL.md +208 -0
  101. package/plugins/src/phaser/skills/phaser-testing/SKILL.md +103 -10
  102. package/tsconfig/phaser.json +8 -1
@@ -1,14 +1,8 @@
1
- /**
2
- * ESLint 9 Flat Config - Phaser Stack
3
- *
4
- * Extends Lisa's TypeScript config with Phaser 4 enforcement: bans Phaser 3
5
- * rendering idioms that were removed in v4 and non-deterministic randomness
6
- * in game code.
7
- * @module configs/eslint/phaser
8
- */
9
1
  import type { Linter } from "eslint";
10
2
  import { defaultIgnores, defaultThresholds } from "./typescript.js";
11
3
  export { defaultIgnores, defaultThresholds };
4
+ declare const phaserPlugin: any;
5
+ export { phaserPlugin };
12
6
  /**
13
7
  * Default ignore patterns for Phaser projects.
14
8
  */
@@ -1 +1 @@
1
- {"version":3,"file":"phaser.d.ts","sourceRoot":"","sources":["../../../src/configs/eslint/phaser.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EACL,cAAc,EACd,iBAAiB,EAElB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,CAAC;AAE7C;;GAEG;AACH,eAAO,MAAM,oBAAoB,UAKhC,CAAC;AA8DF;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAC9B,eAAe,EACf,cAAqC,EACrC,UAA8B,GAC/B,EAAE;IACD,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,iBAAiB,CAAC;CACvC,GAAG,MAAM,CAAC,MAAM,EAAE,CAoBlB"}
1
+ {"version":3,"file":"phaser.d.ts","sourceRoot":"","sources":["../../../src/configs/eslint/phaser.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EACL,cAAc,EACd,iBAAiB,EAElB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,CAAC;AAM7C,QAAA,MAAM,YAAY,KAIjB,CAAC;AAEF,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;GAEG;AACH,eAAO,MAAM,oBAAoB,UAKhC,CAAC;AA4MF;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAC9B,eAAe,EACf,cAAqC,EACrC,UAA8B,GAC/B,EAAE;IACD,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,iBAAiB,CAAC;CACvC,GAAG,MAAM,CAAC,MAAM,EAAE,CAKlB"}
@@ -1,5 +1,26 @@
1
+ /**
2
+ * ESLint 9 Flat Config - Phaser Stack
3
+ *
4
+ * Extends Lisa's TypeScript config with Phaser 4 enforcement:
5
+ * - bans Phaser 3 rendering/API idioms removed in v4,
6
+ * - enforces determinism (no Math.random/Date.now/performance.now in game code),
7
+ * - enforces the architecture boundary (src/logic must be Phaser-free),
8
+ * - bans reusing game.events, shipping physics debug:true, and raw localStorage,
9
+ * - registers eslint-plugin-phaser for the per-frame/lifecycle rules that cannot
10
+ * be expressed as static selectors (no-create-in-update,
11
+ * no-allocation-in-update, require-shutdown-cleanup).
12
+ * @module configs/eslint/phaser
13
+ */
14
+ import { createRequire } from "module";
15
+ import { fileURLToPath } from "url";
1
16
  import { defaultIgnores, defaultThresholds, getTypescriptConfig, } from "./typescript.js";
2
17
  export { defaultIgnores, defaultThresholds };
18
+ // Custom plugin — loaded via relative path so it resolves from the Lisa package
19
+ // itself (eslint-plugin-* dirs are in the published files array) rather than the
20
+ // registry. Same pattern as the code-organization plugin in typescript.ts.
21
+ const require = createRequire(import.meta.url);
22
+ const phaserPlugin = require(fileURLToPath(new URL("../../../eslint-plugin-phaser/index.js", import.meta.url)));
23
+ export { phaserPlugin };
3
24
  /**
4
25
  * Default ignore patterns for Phaser projects.
5
26
  */
@@ -50,10 +71,130 @@ const PHASER_3_IDIOM_BANS = [
50
71
  selector: "NewExpression[callee.object.object.name='Phaser'][callee.object.property.name='Display'][callee.property.name='BitmapMask']",
51
72
  message: "Phaser 4 removed BitmapMask. Use the Mask filter.",
52
73
  },
74
+ {
75
+ selector: "MemberExpression[property.name='Mesh'][object.property.name='GameObjects']",
76
+ message: "Phaser 4 removed Mesh. Use Sprites/Containers, or SpriteGPULayer for mass rendering.",
77
+ },
78
+ {
79
+ selector: "MemberExpression[property.name='Plane'][object.property.name='GameObjects']",
80
+ message: "Phaser 4 removed Plane. Use Sprites/Containers.",
81
+ },
82
+ {
83
+ selector: "MemberExpression[property.name='Camera3D']",
84
+ message: "Phaser 4 removed Camera3D. There is no 3D camera in v4.",
85
+ },
86
+ {
87
+ selector: "MemberExpression[property.name='Layer3D']",
88
+ message: "Phaser 4 removed Layer3D. There is no 3D layer in v4.",
89
+ },
90
+ ];
91
+ /**
92
+ * Determinism and architecture bans for Phaser game code (src/**, non-test).
93
+ */
94
+ const PHASER_GAME_BANS = [
53
95
  {
54
96
  selector: "CallExpression[callee.object.name='Math'][callee.property.name='random']",
55
97
  message: "Game code must be deterministic. Use the seeded Phaser.Math.RND (RandomDataGenerator) instead of Math.random().",
56
98
  },
99
+ {
100
+ selector: "CallExpression[callee.object.name='Date'][callee.property.name='now']",
101
+ message: "Game code must be deterministic. Use the scene clock (update(time, delta)) or seeded RNG, not Date.now().",
102
+ },
103
+ {
104
+ selector: "CallExpression[callee.object.name='performance'][callee.property.name='now']",
105
+ message: "Game code must be deterministic. Use the scene clock (update(time, delta)), not performance.now().",
106
+ },
107
+ {
108
+ selector: "MemberExpression[property.name='events'][object.property.name='game'][object.object.type='ThisExpression']",
109
+ message: "Do not reuse this.game.events as an app bus — it collides with Phaser internals. Create a dedicated EventsCenter (new Phaser.Events.EventEmitter()) in src/services. See the phaser-services skill.",
110
+ },
111
+ {
112
+ selector: ":matches(Property[key.name='physics'], Property[key.name='arcade'], Property[key.name='matter']) Property[key.name='debug'][value.value=true]",
113
+ message: "Never ship physics debug:true. Gate it behind an env flag (e.g. import.meta.env.DEV).",
114
+ },
115
+ ];
116
+ const LOGIC_IMPORT_MESSAGE = "src/logic must be Phaser-free (pure, testable game logic). Keep all Phaser usage in scenes/objects/services and pass plain data into logic.";
117
+ /**
118
+ * Phaser-specific ESLint overrides layered on top of the TypeScript config.
119
+ * Hoisted to module scope so the factory stays small; they close over the
120
+ * module-level bans, plugin, and boundary message (no per-call parameters).
121
+ */
122
+ const PHASER_OVERRIDES = [
123
+ // Game code: idiom bans, determinism, localStorage discipline, and the
124
+ // stateful per-frame/lifecycle rules. Test files are excluded.
125
+ {
126
+ files: ["src/**/*.ts"],
127
+ ignores: ["src/**/*.test.ts", "src/**/*.spec.ts"],
128
+ plugins: { phaser: phaserPlugin },
129
+ rules: {
130
+ "no-restricted-syntax": [
131
+ "error",
132
+ ...PHASER_3_IDIOM_BANS,
133
+ ...PHASER_GAME_BANS,
134
+ ],
135
+ "no-restricted-globals": [
136
+ "error",
137
+ {
138
+ name: "localStorage",
139
+ message: "Use the typed SaveService (src/services), not raw localStorage — it centralizes schema versioning and migration. See the phaser-services skill.",
140
+ },
141
+ {
142
+ name: "sessionStorage",
143
+ message: "Use a typed service (src/services), not raw sessionStorage.",
144
+ },
145
+ ],
146
+ "no-restricted-properties": [
147
+ "error",
148
+ {
149
+ object: "window",
150
+ property: "localStorage",
151
+ message: "Use the typed SaveService (src/services), not window.localStorage.",
152
+ },
153
+ {
154
+ object: "window",
155
+ property: "sessionStorage",
156
+ message: "Use a typed service (src/services), not window.sessionStorage.",
157
+ },
158
+ ],
159
+ "phaser/no-create-in-update": "error",
160
+ "phaser/no-allocation-in-update": "error",
161
+ "phaser/require-shutdown-cleanup": "error",
162
+ },
163
+ },
164
+ // The architecture boundary: pure logic may not import Phaser.
165
+ {
166
+ files: ["src/logic/**/*.ts"],
167
+ ignores: ["src/logic/**/*.test.ts", "src/logic/**/*.spec.ts"],
168
+ rules: {
169
+ "no-restricted-imports": [
170
+ "error",
171
+ {
172
+ paths: [{ name: "phaser", message: LOGIC_IMPORT_MESSAGE }],
173
+ patterns: [
174
+ { group: ["phaser", "phaser/*"], message: LOGIC_IMPORT_MESSAGE },
175
+ ],
176
+ },
177
+ ],
178
+ },
179
+ },
180
+ // Services own the persistence boundary, so they may touch storage directly.
181
+ {
182
+ files: ["src/services/**/*.ts"],
183
+ rules: {
184
+ "no-restricted-globals": "off",
185
+ "no-restricted-properties": "off",
186
+ },
187
+ },
188
+ // Tests can do anything (mock removed APIs, allocate freely, etc.).
189
+ {
190
+ files: ["tests/**/*.ts", "src/**/*.test.ts", "src/**/*.spec.ts"],
191
+ rules: {
192
+ "no-restricted-syntax": "off",
193
+ "no-restricted-imports": "off",
194
+ "no-restricted-globals": "off",
195
+ "no-restricted-properties": "off",
196
+ },
197
+ },
57
198
  ];
58
199
  /**
59
200
  * Creates the Phaser ESLint configuration.
@@ -65,23 +206,8 @@ const PHASER_3_IDIOM_BANS = [
65
206
  */
66
207
  export function getPhaserConfig({ tsconfigRootDir, ignorePatterns = defaultPhaserIgnores, thresholds = defaultThresholds, }) {
67
208
  return [
68
- ...getTypescriptConfig({
69
- tsconfigRootDir,
70
- ignorePatterns,
71
- thresholds,
72
- }),
73
- {
74
- files: ["src/**/*.ts"],
75
- rules: {
76
- "no-restricted-syntax": ["error", ...PHASER_3_IDIOM_BANS],
77
- },
78
- },
79
- {
80
- files: ["tests/**/*.ts", "src/**/*.test.ts"],
81
- rules: {
82
- "no-restricted-syntax": "off",
83
- },
84
- },
209
+ ...getTypescriptConfig({ tsconfigRootDir, ignorePatterns, thresholds }),
210
+ ...PHASER_OVERRIDES,
85
211
  ];
86
212
  }
87
213
  //# sourceMappingURL=phaser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"phaser.js","sourceRoot":"","sources":["../../../src/configs/eslint/phaser.ts"],"names":[],"mappings":"AASA,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,CAAC;AAE7C;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,GAAG,cAAc;IACjB,mBAAmB;IACnB,kBAAkB;IAClB,UAAU;CACX,CAAC;AAEF;;;GAGG;AACH,MAAM,mBAAmB,GAGnB;IACJ;QACE,QAAQ,EAAE,oDAAoD;QAC9D,OAAO,EACL,+HAA+H;KAClI;IACD;QACE,QAAQ,EAAE,wDAAwD;QAClE,OAAO,EACL,uGAAuG;KAC1G;IACD;QACE,QAAQ,EAAE,sDAAsD;QAChE,OAAO,EACL,2EAA2E;KAC9E;IACD;QACE,QAAQ,EAAE,oDAAoD;QAC9D,OAAO,EACL,wFAAwF;KAC3F;IACD;QACE,QAAQ,EAAE,yCAAyC;QACnD,OAAO,EACL,wGAAwG;KAC3G;IACD;QACE,QAAQ,EAAE,0CAA0C;QACpD,OAAO,EACL,yGAAyG;KAC5G;IACD;QACE,QAAQ,EACN,mGAAmG;QACrG,OAAO,EAAE,uDAAuD;KACjE;IACD;QACE,QAAQ,EAAE,gEAAgE;QAC1E,OAAO,EAAE,uDAAuD;KACjE;IACD;QACE,QAAQ,EACN,6HAA6H;QAC/H,OAAO,EAAE,mDAAmD;KAC7D;IACD;QACE,QAAQ,EACN,0EAA0E;QAC5E,OAAO,EACL,iHAAiH;KACpH;CACF,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,eAAe,EACf,cAAc,GAAG,oBAAoB,EACrC,UAAU,GAAG,iBAAiB,GAK/B;IACC,OAAO;QACL,GAAG,mBAAmB,CAAC;YACrB,eAAe;YACf,cAAc;YACd,UAAU;SACX,CAAC;QACF;YACE,KAAK,EAAE,CAAC,aAAa,CAAC;YACtB,KAAK,EAAE;gBACL,sBAAsB,EAAE,CAAC,OAAO,EAAE,GAAG,mBAAmB,CAAC;aAC1D;SACF;QACD;YACE,KAAK,EAAE,CAAC,eAAe,EAAE,kBAAkB,CAAC;YAC5C,KAAK,EAAE;gBACL,sBAAsB,EAAE,KAAK;aAC9B;SACF;KACiB,CAAC;AACvB,CAAC"}
1
+ {"version":3,"file":"phaser.js","sourceRoot":"","sources":["../../../src/configs/eslint/phaser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ # @codyswann/eslint-plugin-phaser
2
+
3
+ ESLint 9 plugin enforcing Phaser 4 performance and lifecycle invariants that
4
+ cannot be expressed as flat `no-restricted-syntax` selectors. It is wired into
5
+ the Phaser stack automatically by `@codyswann/lisa/eslint/phaser`; you do not
6
+ configure it directly.
7
+
8
+ ## Rules
9
+
10
+ | Rule | What it catches |
11
+ | --- | --- |
12
+ | `no-create-in-update` | Creating GameObjects, tweens, timers, or `new Phaser.*` objects inside a Scene `update()` method (per-frame churn). Create in `create()` and pool/reuse. |
13
+ | `no-allocation-in-update` | Heap allocations in `update()` — object/array literals, array-iteration chains (`.map`/`.filter`/`.reduce`/`.flatMap`), and `new` collections. Hoist scratch objects; iterate in place. |
14
+ | `require-shutdown-cleanup` | A Scene that registers persistent external listeners (`this.input`/`this.scale`/`this.game.events`, `window`/`document`) without a cleanup path. Satisfied by a `shutdown()` method or a `this.events.once('shutdown', ...)` handler. |
15
+
16
+ All three are scoped to `src/**` game code (relaxed in tests) by the Lisa Phaser
17
+ ESLint config. Tests for these rules live in the Lisa repo under
18
+ `tests/unit/config/eslint-plugin-phaser/` and run in CI via Vitest + ESLint's
19
+ `RuleTester`.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * This file is managed by Lisa.
3
+ * Do not edit directly — changes will be overwritten on the next `lisa` run.
4
+ */
5
+
6
+ /**
7
+ * ESLint plugin for Phaser 4 game-development standards
8
+ *
9
+ * Stateful rules that enforce Phaser 4 performance and lifecycle invariants that
10
+ * cannot be expressed as flat `no-restricted-syntax` selectors. Supports ESLint 9
11
+ * flat config. Wired into the Phaser stack via @codyswann/lisa/eslint/phaser.
12
+ *
13
+ * Rules:
14
+ * - no-create-in-update: no GameObject/tween/timer/Phaser creation in update()
15
+ * - no-allocation-in-update: no heap allocations in update()
16
+ * - require-shutdown-cleanup: persistent external listeners need a cleanup path
17
+ * @module eslint-plugin-phaser
18
+ */
19
+ const noCreateInUpdate = require("./rules/no-create-in-update");
20
+ const noAllocationInUpdate = require("./rules/no-allocation-in-update");
21
+ const requireShutdownCleanup = require("./rules/require-shutdown-cleanup");
22
+
23
+ const plugin = {
24
+ meta: {
25
+ name: "eslint-plugin-phaser",
26
+ version: "1.0.0",
27
+ },
28
+ rules: {
29
+ "no-create-in-update": noCreateInUpdate,
30
+ "no-allocation-in-update": noAllocationInUpdate,
31
+ "require-shutdown-cleanup": requireShutdownCleanup,
32
+ },
33
+ };
34
+
35
+ module.exports = plugin;
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@codyswann/eslint-plugin-phaser",
3
+ "version": "1.0.0",
4
+ "description": "ESLint plugin for Phaser 4 game-development standards",
5
+ "main": "index.js",
6
+ "publishConfig": { "access": "public" },
7
+ "peerDependencies": {
8
+ "eslint": ">=9.0.0"
9
+ }
10
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * This file is managed by Lisa.
3
+ * Do not edit directly — changes will be overwritten on the next `lisa` run.
4
+ */
5
+
6
+ /**
7
+ * ESLint rule: no-allocation-in-update
8
+ *
9
+ * Flags heap allocations directly inside a Scene's `update(time, delta)` method.
10
+ * `update` runs every frame, so allocations there create GC pressure that
11
+ * stutters the frame budget. Hoist scratch objects/arrays to fields created
12
+ * once in create(), and avoid per-frame array-iteration chains.
13
+ *
14
+ * Detected (lexically in the update method body, not in nested functions):
15
+ * - object literals ({ ... })
16
+ * - array literals ([ ... ])
17
+ * - array-iteration method calls (.map/.filter/.reduce/.flatMap/.flat/.concat/.slice)
18
+ * - new Array / Object / Map / Set / WeakMap / WeakSet / typed arrays
19
+ * @module eslint-plugin-phaser/rules/no-allocation-in-update
20
+ */
21
+
22
+ const ARRAY_ALLOC_METHODS = new Set([
23
+ "map",
24
+ "filter",
25
+ "reduce",
26
+ "reduceRight",
27
+ "flatMap",
28
+ "flat",
29
+ "concat",
30
+ "slice",
31
+ ]);
32
+
33
+ const COLLECTION_CTORS = new Set([
34
+ "Array",
35
+ "Object",
36
+ "Map",
37
+ "Set",
38
+ "WeakMap",
39
+ "WeakSet",
40
+ "Float32Array",
41
+ "Float64Array",
42
+ "Int32Array",
43
+ "Uint8Array",
44
+ "Uint16Array",
45
+ "Uint32Array",
46
+ ]);
47
+
48
+ /**
49
+ * Resolve a property/method key node to its string name.
50
+ * @param {object} key - An Identifier or Literal key node
51
+ * @returns {string|null} The key name, or null if not resolvable
52
+ */
53
+ function keyName(key) {
54
+ if (!key) return null;
55
+ if (key.type === "Identifier") return key.name;
56
+ if (key.type === "Literal") return String(key.value);
57
+ return null;
58
+ }
59
+
60
+ /**
61
+ * Whether a class extends a Phaser Scene (superclass name ends in "Scene", e.g.
62
+ * Phaser.Scene, Scene, BaseScene). Keeps this per-frame rule from firing on
63
+ * unrelated classes that merely happen to define an update() method.
64
+ * @param {object} classNode - A ClassDeclaration/ClassExpression node
65
+ * @returns {boolean} True if the class extends a Scene-like superclass
66
+ */
67
+ function extendsScene(classNode) {
68
+ const sc = classNode.superClass;
69
+ if (!sc) return false;
70
+ if (sc.type === "Identifier") return /Scene$/.test(sc.name);
71
+ if (sc.type === "MemberExpression" && sc.property.type === "Identifier") {
72
+ return /Scene$/.test(sc.property.name);
73
+ }
74
+ return false;
75
+ }
76
+
77
+ /**
78
+ * Whether the nearest enclosing class extends a Phaser Scene.
79
+ * @param {object} node - Any node
80
+ * @returns {boolean} True if an enclosing class extends a Scene
81
+ */
82
+ function enclosingSceneClass(node) {
83
+ const up = n => {
84
+ if (!n) return false;
85
+ if (n.type === "ClassDeclaration" || n.type === "ClassExpression") {
86
+ return extendsScene(n);
87
+ }
88
+ return up(n.parent);
89
+ };
90
+ return up(node.parent);
91
+ }
92
+
93
+ /**
94
+ * Determine whether a function node is the update() method of a Phaser Scene.
95
+ * @param {object} node - A function-like node
96
+ * @returns {boolean} True if it is a Scene update() method/field
97
+ */
98
+ function isUpdateFunction(node) {
99
+ const parent = node.parent;
100
+ if (!parent) return false;
101
+ if (
102
+ (parent.type === "MethodDefinition" ||
103
+ parent.type === "PropertyDefinition") &&
104
+ parent.value === node &&
105
+ keyName(parent.key) === "update"
106
+ ) {
107
+ return enclosingSceneClass(node);
108
+ }
109
+ return false;
110
+ }
111
+
112
+ module.exports = {
113
+ meta: {
114
+ type: "problem",
115
+ docs: {
116
+ description:
117
+ "Disallow heap allocations (object/array literals, array-iteration chains, new collections) inside a Scene update() method",
118
+ category: "Best Practices",
119
+ recommended: true,
120
+ },
121
+ fixable: null,
122
+ schema: [],
123
+ messages: {
124
+ objectLiteral:
125
+ "Object literal allocated in update() (runs every frame). Hoist a reusable scratch object to a field set in create().",
126
+ arrayLiteral:
127
+ "Array literal allocated in update() (runs every frame). Hoist a reusable array, or iterate an existing one in place.",
128
+ arrayMethod:
129
+ "Array-iteration method '{{name}}' allocates in update() (runs every frame). Use a plain for-loop over the existing array.",
130
+ newCollection:
131
+ "new {{name}} allocated in update() (runs every frame). Create the collection once in create() and clear/reuse it.",
132
+ },
133
+ },
134
+
135
+ create(context) {
136
+ const fnStack = [];
137
+ const inUpdateBody = () =>
138
+ fnStack.length > 0 && fnStack[fnStack.length - 1];
139
+ const enter = node => fnStack.push(isUpdateFunction(node));
140
+ const exit = () => fnStack.pop();
141
+
142
+ return {
143
+ FunctionDeclaration: enter,
144
+ "FunctionDeclaration:exit": exit,
145
+ FunctionExpression: enter,
146
+ "FunctionExpression:exit": exit,
147
+ ArrowFunctionExpression: enter,
148
+ "ArrowFunctionExpression:exit": exit,
149
+
150
+ ObjectExpression(node) {
151
+ if (inUpdateBody())
152
+ context.report({ node, messageId: "objectLiteral" });
153
+ },
154
+
155
+ ArrayExpression(node) {
156
+ if (inUpdateBody()) context.report({ node, messageId: "arrayLiteral" });
157
+ },
158
+
159
+ CallExpression(node) {
160
+ if (!inUpdateBody()) return;
161
+ if (
162
+ node.callee.type === "MemberExpression" &&
163
+ node.callee.property.type === "Identifier" &&
164
+ ARRAY_ALLOC_METHODS.has(node.callee.property.name)
165
+ ) {
166
+ context.report({
167
+ node,
168
+ messageId: "arrayMethod",
169
+ data: { name: node.callee.property.name },
170
+ });
171
+ }
172
+ },
173
+
174
+ NewExpression(node) {
175
+ if (!inUpdateBody()) return;
176
+ if (
177
+ node.callee.type === "Identifier" &&
178
+ COLLECTION_CTORS.has(node.callee.name)
179
+ ) {
180
+ context.report({
181
+ node,
182
+ messageId: "newCollection",
183
+ data: { name: node.callee.name },
184
+ });
185
+ }
186
+ },
187
+ };
188
+ },
189
+ };
@@ -0,0 +1,200 @@
1
+ /**
2
+ * This file is managed by Lisa.
3
+ * Do not edit directly — changes will be overwritten on the next `lisa` run.
4
+ */
5
+
6
+ /**
7
+ * ESLint rule: no-create-in-update
8
+ *
9
+ * Flags creation of GameObjects, tweens, timers, and Phaser objects inside a
10
+ * Scene's `update(time, delta)` method. `update` runs every frame; creating
11
+ * objects there churns the heap and stutters the frame budget. Create in
12
+ * `create()` and reuse (pool) instead.
13
+ *
14
+ * Detected (when lexically in the update method body, not in nested functions):
15
+ * - this.add.* / this.make.* (GameObjectFactory / GameObjectCreator)
16
+ * - this.physics.add.* (Arcade factory)
17
+ * - this.tweens.add/addCounter/chain/create (Tween creation)
18
+ * - this.time.addEvent/delayedCall/addLoop (Timer creation)
19
+ * - this.sound.add / this.anims.create / this.particles.add
20
+ * - new Phaser.* (Phaser object allocation)
21
+ * @module eslint-plugin-phaser/rules/no-create-in-update
22
+ */
23
+
24
+ /**
25
+ * Resolve a property/method key node to its string name.
26
+ * @param {object} key - An Identifier or Literal key node
27
+ * @returns {string|null} The key name, or null if not resolvable
28
+ */
29
+ function keyName(key) {
30
+ if (!key) return null;
31
+ if (key.type === "Identifier") return key.name;
32
+ if (key.type === "Literal") return String(key.value);
33
+ return null;
34
+ }
35
+
36
+ /**
37
+ * Decode a MemberExpression chain into its root node and ordered property names.
38
+ * @param {object} node - A MemberExpression node
39
+ * @returns {{root: object, props: (string|null)[]}} Root node and property names
40
+ */
41
+ function memberInfo(node) {
42
+ const props = [];
43
+ /**
44
+ * Walk up the member chain, collecting property names.
45
+ * @param {object} cur - Current node in the chain
46
+ * @returns {object} The non-member root node
47
+ */
48
+ const walk = cur => {
49
+ if (cur && cur.type === "MemberExpression") {
50
+ props.unshift(
51
+ cur.property && cur.property.type === "Identifier"
52
+ ? cur.property.name
53
+ : null
54
+ );
55
+ return walk(cur.object);
56
+ }
57
+ return cur;
58
+ };
59
+ return { root: walk(node), props };
60
+ }
61
+
62
+ /**
63
+ * Whether a class extends a Phaser Scene (superclass name ends in "Scene", e.g.
64
+ * Phaser.Scene, Scene, BaseScene). Keeps this per-frame rule from firing on
65
+ * unrelated classes that merely happen to define an update() method.
66
+ * @param {object} classNode - A ClassDeclaration/ClassExpression node
67
+ * @returns {boolean} True if the class extends a Scene-like superclass
68
+ */
69
+ function extendsScene(classNode) {
70
+ const sc = classNode.superClass;
71
+ if (!sc) return false;
72
+ if (sc.type === "Identifier") return /Scene$/.test(sc.name);
73
+ if (sc.type === "MemberExpression" && sc.property.type === "Identifier") {
74
+ return /Scene$/.test(sc.property.name);
75
+ }
76
+ return false;
77
+ }
78
+
79
+ /**
80
+ * Whether the nearest enclosing class extends a Phaser Scene.
81
+ * @param {object} node - Any node
82
+ * @returns {boolean} True if an enclosing class extends a Scene
83
+ */
84
+ function enclosingSceneClass(node) {
85
+ const up = n => {
86
+ if (!n) return false;
87
+ if (n.type === "ClassDeclaration" || n.type === "ClassExpression") {
88
+ return extendsScene(n);
89
+ }
90
+ return up(n.parent);
91
+ };
92
+ return up(node.parent);
93
+ }
94
+
95
+ /**
96
+ * Determine whether a function node is the update() method of a Phaser Scene.
97
+ * @param {object} node - A function-like node
98
+ * @returns {boolean} True if it is a Scene update() method/field
99
+ */
100
+ function isUpdateFunction(node) {
101
+ const parent = node.parent;
102
+ if (!parent) return false;
103
+ if (
104
+ (parent.type === "MethodDefinition" ||
105
+ parent.type === "PropertyDefinition") &&
106
+ parent.value === node &&
107
+ keyName(parent.key) === "update"
108
+ ) {
109
+ return enclosingSceneClass(node);
110
+ }
111
+ return false;
112
+ }
113
+
114
+ /**
115
+ * Patterns that create new objects, keyed by the leading `this.<segment>` chain.
116
+ * @param {(string|null)[]} props - Ordered property names from `this`
117
+ * @returns {boolean} True if the chain is a known creation call
118
+ */
119
+ function isCreationChain(props) {
120
+ const [a, b] = props;
121
+ if (a === "add" || a === "make") return true;
122
+ if (a === "physics" && b === "add") return true;
123
+ if (
124
+ a === "tweens" &&
125
+ (b === "add" || b === "addCounter" || b === "chain" || b === "create")
126
+ ) {
127
+ return true;
128
+ }
129
+ if (
130
+ a === "time" &&
131
+ (b === "addEvent" || b === "delayedCall" || b === "addLoop")
132
+ )
133
+ return true;
134
+ if (a === "sound" && b === "add") return true;
135
+ if (a === "anims" && b === "create") return true;
136
+ if (a === "particles" && b === "add") return true;
137
+ return false;
138
+ }
139
+
140
+ module.exports = {
141
+ meta: {
142
+ type: "problem",
143
+ docs: {
144
+ description:
145
+ "Disallow creating GameObjects, tweens, timers, or Phaser objects inside a Scene update() method",
146
+ category: "Best Practices",
147
+ recommended: true,
148
+ },
149
+ fixable: null,
150
+ schema: [],
151
+ messages: {
152
+ createInUpdate:
153
+ "Do not create objects in update() — it runs every frame. Create in create() and pool/reuse (Group.get / killAndHide).",
154
+ newInUpdate:
155
+ "Do not allocate Phaser objects (new Phaser.*) in update() — it runs every frame. Hoist a reusable scratch instance to create().",
156
+ },
157
+ },
158
+
159
+ create(context) {
160
+ const fnStack = [];
161
+ const inUpdateBody = () =>
162
+ fnStack.length > 0 && fnStack[fnStack.length - 1];
163
+
164
+ /**
165
+ * Push function scope state on entry.
166
+ * @param {object} node - The function node being entered
167
+ * @returns {void}
168
+ */
169
+ const enter = node => fnStack.push(isUpdateFunction(node));
170
+ const exit = () => fnStack.pop();
171
+
172
+ return {
173
+ FunctionDeclaration: enter,
174
+ "FunctionDeclaration:exit": exit,
175
+ FunctionExpression: enter,
176
+ "FunctionExpression:exit": exit,
177
+ ArrowFunctionExpression: enter,
178
+ "ArrowFunctionExpression:exit": exit,
179
+
180
+ CallExpression(node) {
181
+ if (!inUpdateBody()) return;
182
+ if (node.callee.type !== "MemberExpression") return;
183
+ const { root, props } = memberInfo(node.callee);
184
+ if (root.type !== "ThisExpression") return;
185
+ if (isCreationChain(props)) {
186
+ context.report({ node, messageId: "createInUpdate" });
187
+ }
188
+ },
189
+
190
+ NewExpression(node) {
191
+ if (!inUpdateBody()) return;
192
+ if (node.callee.type !== "MemberExpression") return;
193
+ const { root } = memberInfo(node.callee);
194
+ if (root.type === "Identifier" && root.name === "Phaser") {
195
+ context.report({ node, messageId: "newInUpdate" });
196
+ }
197
+ },
198
+ };
199
+ },
200
+ };