@webjsdev/server 0.8.52 → 0.8.53

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/check.js +42 -46
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/server",
3
- "version": "0.8.52",
3
+ "version": "0.8.53",
4
4
  "type": "module",
5
5
  "description": "webjs dev/prod server: SSR, router, API, server actions, live reload",
6
6
  "main": "index.js",
package/src/check.js CHANGED
@@ -235,74 +235,70 @@ function isArrayTypeText(type) {
235
235
  function findFieldInitializers(classBody, props) {
236
236
  /** @type {string[]} */
237
237
  const out = [];
238
- // Walk the body, tracking brace depth. At depth 0, look for
239
- // class-field-shaped lines.
238
+ const n = classBody.length;
239
+ // Match class-field declarations. Two shapes:
240
+ // 1. With initializer: `count = 0`, `count: number = 0`, `public count = 0`
241
+ // 2. Type-only (no initializer): `count!: number`, `count?: number`, `count: number`
242
+ // Both compile to Object.defineProperty after super() under modern class-field
243
+ // semantics, clobbering the reactive accessor.
244
+ // The initializer regex: optional modifier, identifier, optional type, then `=`.
245
+ const initRe = /^\s*(?:(public|private|protected|readonly)\s+)?([A-Za-z_$][\w$]*)\s*(?::\s*[^=;]+)?\s*=\s*[^=>]/;
246
+ // The type-only regex: optional modifier, identifier, then `!:` or `?:` or `:` with a type.
247
+ const typeOnlyRe = /^\s*(?:(public|private|protected|readonly)\s+)?([A-Za-z_$][\w$]*)\s*[!?]?\s*:\s*\S/;
248
+ const examineLine = (lineStart) => {
249
+ let j = lineStart;
250
+ while (j < n && classBody[j] !== '\n') j++;
251
+ const line = classBody.slice(lineStart, j);
252
+ const initM = initRe.exec(line);
253
+ const typeM = typeOnlyRe.exec(line);
254
+ // Prefer the initializer match; if neither, skip.
255
+ const name = initM ? initM[2] : (typeM ? typeM[2] : null);
256
+ // `declare`, `static`, and `this.` patterns shouldn't reach here
257
+ // (declare/static start with their keyword, this.x has the dot in
258
+ // the regex group), but guard against matching keywords as names:
259
+ if (name && name !== 'declare' && name !== 'static' && props.has(name)) out.push(name);
260
+ };
261
+ // Walk the body char by char, tracking brace depth. A class field lives at
262
+ // the class-body top level (depth 0). We examine a line ONCE, at its first
263
+ // non-whitespace char, only while depth is 0, and WITHOUT skipping the braces
264
+ // on that line: an opening brace on the line (`method() {`, `field = {`) must
265
+ // still be counted so the lines inside its block are seen at depth > 0. The
266
+ // earlier version jumped `i` past the whole examined line, which dropped that
267
+ // line's braces and let object-literal keys inside a method body (`{ game:
268
+ // ..., scoreboard: ... }`) be misread as depth-0 class fields (#934).
240
269
  let depth = 0;
241
270
  let i = 0;
242
271
  let lineStart = 0;
272
+ let examined = false;
243
273
  let str = '';
244
- while (i < classBody.length) {
274
+ while (i < n) {
245
275
  const c = classBody[i];
246
276
  if (str) {
247
277
  if (c === '\\') { i += 2; continue; }
248
278
  if (c === str) str = '';
249
- else if (str === '`' && c === '$' && classBody[i + 1] === '{') {
250
- depth++;
251
- i += 2;
252
- continue;
253
- }
254
- i++;
255
- continue;
256
- }
257
- if (c === '\n') {
258
- lineStart = i + 1;
259
279
  i++;
260
280
  continue;
261
281
  }
282
+ if (c === '\n') { lineStart = i + 1; examined = false; i++; continue; }
262
283
  if (c === '/' && classBody[i + 1] === '/') {
263
- while (i < classBody.length && classBody[i] !== '\n') i++;
284
+ while (i < n && classBody[i] !== '\n') i++;
264
285
  continue;
265
286
  }
266
287
  if (c === '/' && classBody[i + 1] === '*') {
267
288
  i += 2;
268
- while (i < classBody.length && !(classBody[i] === '*' && classBody[i + 1] === '/')) i++;
289
+ while (i < n && !(classBody[i] === '*' && classBody[i + 1] === '/')) i++;
269
290
  i += 2;
270
291
  continue;
271
292
  }
272
293
  if (c === "'" || c === '"' || c === '`') { str = c; i++; continue; }
294
+ // First non-whitespace char of a class-body top-level line: examine it for
295
+ // a field declaration BEFORE consuming any brace it opens.
296
+ if (depth === 0 && !examined && !/\s/.test(c)) {
297
+ examined = true;
298
+ examineLine(lineStart);
299
+ }
273
300
  if (c === '{') { depth++; i++; continue; }
274
301
  if (c === '}') { depth--; i++; continue; }
275
- // At class-body top level, examine candidate lines starting at lineStart.
276
- if (depth === 0 && i === lineStart || (depth === 0 && /\s/.test(c) && i === lineStart)) {
277
- // Take the rest of the line up to a newline.
278
- let j = lineStart;
279
- while (j < classBody.length && classBody[j] !== '\n') j++;
280
- const line = classBody.slice(lineStart, j);
281
- // Match class-field declarations. Two shapes:
282
- // 1. With initializer: `count = 0`, `count: number = 0`, `public count = 0`
283
- // 2. Type-only (no initializer): `count!: number`, `count?: number`, `count: number`
284
- // Both compile to Object.defineProperty after super() under modern class-field
285
- // semantics, clobbering the reactive accessor.
286
- // The initializer regex: optional modifier, identifier, optional type, then `=`.
287
- const initRe = /^\s*(?:(public|private|protected|readonly)\s+)?([A-Za-z_$][\w$]*)\s*(?::\s*[^=;]+)?\s*=\s*[^=>]/;
288
- // The type-only regex: optional modifier, identifier, then `!:` or `?:` or `:` with a type.
289
- const typeOnlyRe = /^\s*(?:(public|private|protected|readonly)\s+)?([A-Za-z_$][\w$]*)\s*[!?]?\s*:\s*\S/;
290
- const initM = initRe.exec(line);
291
- const typeM = typeOnlyRe.exec(line);
292
- // Prefer the initializer match; if neither, skip.
293
- const name = initM ? initM[2] : (typeM ? typeM[2] : null);
294
- if (name) {
295
- // `declare`, `static`, and `this.` patterns shouldn't reach here
296
- // (declare/static start with their keyword, this.x has the dot in
297
- // the regex group), but guard against matching keywords as names:
298
- if (name !== 'declare' && name !== 'static' && props.has(name)) {
299
- out.push(name);
300
- }
301
- }
302
- // Advance past this line so we don't re-match.
303
- i = j;
304
- continue;
305
- }
306
302
  i++;
307
303
  }
308
304
  return out;