@patterkit/core 0.1.5 → 0.2.1

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/dist/index.cjs CHANGED
@@ -31,11 +31,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  canonicalStringify: () => canonicalStringify,
34
- effectiveGameId: () => import_model3.effectiveGameId,
35
- gameIdify: () => import_model3.gameIdify,
34
+ effectiveGameId: () => import_model4.effectiveGameId,
35
+ gameIdify: () => import_model4.gameIdify,
36
36
  hash32: () => hash32,
37
37
  hash4: () => hash4,
38
- isValidGameId: () => import_model3.isValidGameId,
38
+ isValidGameId: () => import_model4.isValidGameId,
39
39
  newId: () => newId,
40
40
  parseSource: () => parseSource,
41
41
  slug: () => slug,
@@ -81,7 +81,7 @@ function hash32(input) {
81
81
  }
82
82
 
83
83
  // src/index.ts
84
- var import_model3 = require("@patterkit/model");
84
+ var import_model4 = require("@patterkit/model");
85
85
 
86
86
  // src/serialize.ts
87
87
  var import_json5 = __toESM(require("json5"), 1);
@@ -120,6 +120,7 @@ ${indent}}`;
120
120
  // src/validate.ts
121
121
  var import_model = require("@patterkit/model");
122
122
  var import_model2 = require("@patterkit/model");
123
+ var import_model3 = require("@patterkit/model");
123
124
  function choiceLoopsBack(choice, ...targets) {
124
125
  const back = new Set(targets.filter((s) => typeof s === "string" && s.length > 0));
125
126
  const hits = (n) => n.type === "snippet" ? !!n.jump && back.has(n.jump.to) : (n.children ?? []).some(hits);
@@ -307,6 +308,18 @@ function validateProject(input) {
307
308
  }
308
309
  function validateProjectFile(project, issues) {
309
310
  checkDecls(project.properties, "project properties", issues);
311
+ for (const scope of project.scopeRegistry?.scopes ?? []) {
312
+ const where = `host scope '@${scope.token}'`;
313
+ for (const decl of scope.declarations ?? []) {
314
+ const name = decl.name?.toLowerCase();
315
+ if (!name) {
316
+ issues.push({ code: "invalid-declaration", message: `${where}: a property declaration has no name` });
317
+ continue;
318
+ }
319
+ const problem = propertyNameProblem(decl.name);
320
+ if (problem) issues.push({ code: "invalid-declaration", message: `${where}: ${problem}` });
321
+ }
322
+ }
310
323
  for (const decl of project.properties ?? []) {
311
324
  if (decl.temporary) {
312
325
  issues.push({
@@ -410,6 +423,23 @@ function checkLadderNames(names, where, issues) {
410
423
  seen.add(name);
411
424
  }
412
425
  }
426
+ function propertyNameProblem(raw) {
427
+ if ((0, import_model3.isValidPropertyName)(raw)) return void 0;
428
+ const suggestion = (0, import_model3.propertyNameify)(raw);
429
+ const tail = suggestion ? ` Try '${suggestion}'.` : "";
430
+ const q = `property name '${raw}'`;
431
+ if (import_model3.RESERVED_PROPERTY_NAMES.includes(raw.toLowerCase())) {
432
+ return `${q} is a keyword in expressions ('${raw.toLowerCase()}'), so a reference to it will not parse.${tail}`;
433
+ }
434
+ if (raw.includes("-")) {
435
+ return `${q} contains a hyphen, which an expression reads as subtraction: a reference would compile to a subtraction, not to this property.${tail}`;
436
+ }
437
+ if (/^[0-9]/.test(raw)) return `${q} starts with a digit, which an expression cannot parse as a name.${tail}`;
438
+ if (raw !== raw.toLowerCase() && (0, import_model3.isValidPropertyName)(raw.toLowerCase())) {
439
+ return `${q} must be lower case: expressions fold names, so a reference looks for '${raw.toLowerCase()}' and finds nothing.${tail}`;
440
+ }
441
+ return `${q} can only hold lower case letters, digits and underscores.${tail}`;
442
+ }
413
443
  function checkDecls(decls, where, issues) {
414
444
  const seen = /* @__PURE__ */ new Set();
415
445
  for (const decl of decls ?? []) {
@@ -422,12 +452,29 @@ function checkDecls(decls, where, issues) {
422
452
  issues.push({ code: "invalid-declaration", message: `${where}: duplicate property '${decl.name}'` });
423
453
  }
424
454
  seen.add(name);
455
+ const problem = propertyNameProblem(decl.name);
456
+ if (problem) issues.push({ code: "invalid-declaration", message: `${where}: ${problem}` });
425
457
  if ((decl.type === "enum" || decl.type === "flags") && !decl.values?.length) {
426
458
  issues.push({
427
459
  code: "invalid-declaration",
428
460
  message: `${where}: '${decl.name}' is ${decl.type} but declares no values`
429
461
  });
430
462
  }
463
+ if (decl.type === "quality") {
464
+ const stages = decl.stages ?? [];
465
+ if (stages.length < 2) {
466
+ issues.push({
467
+ code: "invalid-declaration",
468
+ message: `${where}: '${decl.name}' is a quality but declares ${stages.length === 0 ? "no stages" : "only one stage"} (a ladder needs at least two)`
469
+ });
470
+ }
471
+ const seenStages = /* @__PURE__ */ new Set();
472
+ for (const s of stages) {
473
+ if (!s.trim()) issues.push({ code: "invalid-declaration", message: `${where}: '${decl.name}' has an empty stage name` });
474
+ else if (seenStages.has(s)) issues.push({ code: "invalid-declaration", message: `${where}: '${decl.name}' declares stage '${s}' more than once` });
475
+ else seenStages.add(s);
476
+ }
477
+ }
431
478
  if (decl.default !== void 0 && !defaultMatchesType(decl.default, decl)) {
432
479
  issues.push({
433
480
  code: "invalid-declaration",
@@ -448,6 +495,8 @@ function defaultMatchesType(value, decl) {
448
495
  return typeof value === "string" && (decl.values?.includes(value) ?? true);
449
496
  case "flags":
450
497
  return Array.isArray(value) && value.every((v) => typeof v === "string" && (decl.values?.includes(v) ?? true));
498
+ case "quality":
499
+ return typeof value === "string" && (decl.stages?.includes(value) ?? true);
451
500
  }
452
501
  }
453
502
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.js CHANGED
@@ -75,6 +75,7 @@ ${indent}}`;
75
75
  // src/validate.ts
76
76
  import { walkNodes, DEFAULT_WRITING_STATUSES, DEFAULT_RECORDING_STATUSES, DEFAULT_DOCUMENTATION_CLASSES } from "@patterkit/model";
77
77
  import { isValidGameId, effectiveGameId } from "@patterkit/model";
78
+ import { isValidPropertyName, propertyNameify, RESERVED_PROPERTY_NAMES } from "@patterkit/model";
78
79
  function choiceLoopsBack(choice, ...targets) {
79
80
  const back = new Set(targets.filter((s) => typeof s === "string" && s.length > 0));
80
81
  const hits = (n) => n.type === "snippet" ? !!n.jump && back.has(n.jump.to) : (n.children ?? []).some(hits);
@@ -262,6 +263,18 @@ function validateProject(input) {
262
263
  }
263
264
  function validateProjectFile(project, issues) {
264
265
  checkDecls(project.properties, "project properties", issues);
266
+ for (const scope of project.scopeRegistry?.scopes ?? []) {
267
+ const where = `host scope '@${scope.token}'`;
268
+ for (const decl of scope.declarations ?? []) {
269
+ const name = decl.name?.toLowerCase();
270
+ if (!name) {
271
+ issues.push({ code: "invalid-declaration", message: `${where}: a property declaration has no name` });
272
+ continue;
273
+ }
274
+ const problem = propertyNameProblem(decl.name);
275
+ if (problem) issues.push({ code: "invalid-declaration", message: `${where}: ${problem}` });
276
+ }
277
+ }
265
278
  for (const decl of project.properties ?? []) {
266
279
  if (decl.temporary) {
267
280
  issues.push({
@@ -365,6 +378,23 @@ function checkLadderNames(names, where, issues) {
365
378
  seen.add(name);
366
379
  }
367
380
  }
381
+ function propertyNameProblem(raw) {
382
+ if (isValidPropertyName(raw)) return void 0;
383
+ const suggestion = propertyNameify(raw);
384
+ const tail = suggestion ? ` Try '${suggestion}'.` : "";
385
+ const q = `property name '${raw}'`;
386
+ if (RESERVED_PROPERTY_NAMES.includes(raw.toLowerCase())) {
387
+ return `${q} is a keyword in expressions ('${raw.toLowerCase()}'), so a reference to it will not parse.${tail}`;
388
+ }
389
+ if (raw.includes("-")) {
390
+ return `${q} contains a hyphen, which an expression reads as subtraction: a reference would compile to a subtraction, not to this property.${tail}`;
391
+ }
392
+ if (/^[0-9]/.test(raw)) return `${q} starts with a digit, which an expression cannot parse as a name.${tail}`;
393
+ if (raw !== raw.toLowerCase() && isValidPropertyName(raw.toLowerCase())) {
394
+ return `${q} must be lower case: expressions fold names, so a reference looks for '${raw.toLowerCase()}' and finds nothing.${tail}`;
395
+ }
396
+ return `${q} can only hold lower case letters, digits and underscores.${tail}`;
397
+ }
368
398
  function checkDecls(decls, where, issues) {
369
399
  const seen = /* @__PURE__ */ new Set();
370
400
  for (const decl of decls ?? []) {
@@ -377,12 +407,29 @@ function checkDecls(decls, where, issues) {
377
407
  issues.push({ code: "invalid-declaration", message: `${where}: duplicate property '${decl.name}'` });
378
408
  }
379
409
  seen.add(name);
410
+ const problem = propertyNameProblem(decl.name);
411
+ if (problem) issues.push({ code: "invalid-declaration", message: `${where}: ${problem}` });
380
412
  if ((decl.type === "enum" || decl.type === "flags") && !decl.values?.length) {
381
413
  issues.push({
382
414
  code: "invalid-declaration",
383
415
  message: `${where}: '${decl.name}' is ${decl.type} but declares no values`
384
416
  });
385
417
  }
418
+ if (decl.type === "quality") {
419
+ const stages = decl.stages ?? [];
420
+ if (stages.length < 2) {
421
+ issues.push({
422
+ code: "invalid-declaration",
423
+ message: `${where}: '${decl.name}' is a quality but declares ${stages.length === 0 ? "no stages" : "only one stage"} (a ladder needs at least two)`
424
+ });
425
+ }
426
+ const seenStages = /* @__PURE__ */ new Set();
427
+ for (const s of stages) {
428
+ if (!s.trim()) issues.push({ code: "invalid-declaration", message: `${where}: '${decl.name}' has an empty stage name` });
429
+ else if (seenStages.has(s)) issues.push({ code: "invalid-declaration", message: `${where}: '${decl.name}' declares stage '${s}' more than once` });
430
+ else seenStages.add(s);
431
+ }
432
+ }
386
433
  if (decl.default !== void 0 && !defaultMatchesType(decl.default, decl)) {
387
434
  issues.push({
388
435
  code: "invalid-declaration",
@@ -403,6 +450,8 @@ function defaultMatchesType(value, decl) {
403
450
  return typeof value === "string" && (decl.values?.includes(value) ?? true);
404
451
  case "flags":
405
452
  return Array.isArray(value) && value.every((v) => typeof v === "string" && (decl.values?.includes(v) ?? true));
453
+ case "quality":
454
+ return typeof value === "string" && (decl.stages?.includes(value) ?? true);
406
455
  }
407
456
  }
408
457
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patterkit/core",
3
- "version": "0.1.5",
3
+ "version": "0.2.1",
4
4
  "description": "Patter core: stable id generation, readable-handle computation, canonical (de)serialisation, and the structural validator.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,7 +34,7 @@
34
34
  "build": "tsup src/index.ts --format esm,cjs --dts --clean"
35
35
  },
36
36
  "dependencies": {
37
- "@patterkit/model": "0.2.1",
37
+ "@patterkit/model": "0.4.0",
38
38
  "json5": "^2.2.3"
39
39
  }
40
40
  }