@abco20/btxml-checker 0.1.2 → 0.1.3

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/server.cjs CHANGED
@@ -15932,7 +15932,8 @@ var DEFAULT_RESOLVED_BTXML_CONFIG = {
15932
15932
  files: [],
15933
15933
  augmentations: [],
15934
15934
  definitions: [],
15935
- inline: {}
15935
+ inline: {},
15936
+ convention: "allow-unused"
15936
15937
  },
15937
15938
  linter: {
15938
15939
  enabled: true,
@@ -16425,6 +16426,16 @@ function stripSubTreeReferenceAst(def) {
16425
16426
  parentBehaviorTreeId: def.parentBehaviorTreeId
16426
16427
  };
16427
16428
  }
16429
+ function stripNodeUsageAst(def) {
16430
+ return {
16431
+ id: def.id,
16432
+ uri: def.uri,
16433
+ kind: def.kind,
16434
+ range: def.range,
16435
+ elementRange: def.elementRange,
16436
+ parentBehaviorTreeId: def.parentBehaviorTreeId
16437
+ };
16438
+ }
16428
16439
  function stripBlackboardReferenceAst(def) {
16429
16440
  return {
16430
16441
  raw: def.raw,
@@ -16445,12 +16456,56 @@ function toPublicDocumentModel(input) {
16445
16456
  kind: input.kind,
16446
16457
  behaviorTrees: input.behaviorTrees.map(stripBehaviorTreeAst),
16447
16458
  subtreeReferences: input.subtreeReferences.map(stripSubTreeReferenceAst),
16459
+ nodeUsages: input.nodeUsages.map(stripNodeUsageAst),
16448
16460
  blackboardReferences: input.blackboardReferences.map(stripBlackboardReferenceAst),
16449
16461
  treeNodesModel: input.treeNodesModel.map(stripTreeNodeModelAst),
16450
16462
  genericSubTreePorts: input.genericSubTreePorts.map(stripPortAst),
16451
16463
  rootMainTreeToExecute: cloneAttributeValueRef(input.rootMainTreeToExecute)
16452
16464
  };
16453
16465
  }
16466
+ function extractNodeUsages(root, uri) {
16467
+ if (root.name === "TreeNodesModel") return [];
16468
+ const usages = [];
16469
+ const walk2 = (node, parentBehaviorTreeId, inTreeNodesModel = false) => {
16470
+ if (inTreeNodesModel) return;
16471
+ const nextInTreeNodesModel = node.name === "TreeNodesModel";
16472
+ const currentBehaviorTreeId = node.name === "BehaviorTree" ? getAttr(node, "ID")?.value ?? parentBehaviorTreeId : parentBehaviorTreeId;
16473
+ const isInsideBehaviorTree = currentBehaviorTreeId !== void 0;
16474
+ if (node.name === "SubTree") {
16475
+ const idAttr = getAttr(node, "ID");
16476
+ if (idAttr && isInsideBehaviorTree) {
16477
+ usages.push({
16478
+ id: idAttr.value,
16479
+ kind: "SubTree",
16480
+ uri,
16481
+ range: node.range,
16482
+ elementRange: node.range,
16483
+ parentBehaviorTreeId: currentBehaviorTreeId
16484
+ });
16485
+ }
16486
+ } else if (isInsideBehaviorTree && node.name !== "root" && node.name !== "BehaviorTree" && node.name !== "TreeNodesModel") {
16487
+ usages.push({
16488
+ id: node.name,
16489
+ kind: "node",
16490
+ uri,
16491
+ range: node.range,
16492
+ elementRange: node.range,
16493
+ parentBehaviorTreeId: currentBehaviorTreeId
16494
+ });
16495
+ }
16496
+ for (const child of node.children || []) {
16497
+ if (child.kind === "element") {
16498
+ walk2(child, currentBehaviorTreeId, nextInTreeNodesModel);
16499
+ }
16500
+ }
16501
+ };
16502
+ for (const child of root.children || []) {
16503
+ if (child.kind === "element") {
16504
+ walk2(child);
16505
+ }
16506
+ }
16507
+ return usages;
16508
+ }
16454
16509
  function addTreeNodeModelToCollections(input) {
16455
16510
  if (input.node.kind === "SubTree" && input.node.id === "SubTree") {
16456
16511
  input.genericSubTreePorts.push(...input.node.ports);
@@ -16502,6 +16557,7 @@ function extractDocumentModel(document, options) {
16502
16557
  }
16503
16558
  }
16504
16559
  const subtreeReferences = root ? extractSubTreeReferences(root, uri) : [];
16560
+ const nodeUsages = root ? extractNodeUsages(root, uri) : [];
16505
16561
  const blackboardReferences = root ? (() => {
16506
16562
  const refs = [];
16507
16563
  collectBlackboardReferences(root, refs, uri, document.originalText);
@@ -16515,6 +16571,7 @@ function extractDocumentModel(document, options) {
16515
16571
  kind,
16516
16572
  behaviorTrees,
16517
16573
  subtreeReferences,
16574
+ nodeUsages,
16518
16575
  blackboardReferences,
16519
16576
  treeNodesModel,
16520
16577
  genericSubTreePorts,
@@ -34589,12 +34646,14 @@ var resolverConfigSchema = external_exports.object({
34589
34646
  includes: resolverIncludesConfigSchema.optional(),
34590
34647
  behaviorTreeIds: external_exports.enum(["workspace-unique", "file-local-first", "allow-ambiguous"]).optional()
34591
34648
  }).strict();
34649
+ var modelConventionSchema = external_exports.enum(["allow-unused", "used-only", "single-source"]);
34592
34650
  var modelsConfigSchema = external_exports.object({
34593
34651
  builtins: external_exports.array(external_exports.enum(SUPPORTED_BUILTIN_MODEL_SETS)).optional(),
34594
34652
  files: external_exports.array(external_exports.string()).optional(),
34595
34653
  augmentations: external_exports.array(external_exports.string()).optional(),
34596
34654
  definitions: external_exports.array(external_exports.string()).optional(),
34597
- inline: external_exports.record(external_exports.string(), configNodeModelSchema).optional()
34655
+ inline: external_exports.record(external_exports.string(), configNodeModelSchema).optional(),
34656
+ convention: modelConventionSchema.optional()
34598
34657
  }).strict();
34599
34658
  var linterSuppressionsConfigSchema = external_exports.object({
34600
34659
  inline: external_exports.enum(["allow", "deny"]).optional()
@@ -34931,6 +34990,9 @@ var RuleCodes = {
34931
34990
  AugmentTargetNotFound: "BT117_AUGMENT_TARGET_NOT_FOUND",
34932
34991
  AugmentPortNotFound: "BT118_AUGMENT_PORT_NOT_FOUND",
34933
34992
  InvalidTypeRefinement: "BT119_INVALID_TYPE_REFINEMENT",
34993
+ ConflictingModelKind: "BT120_CONFLICTING_MODEL_KIND",
34994
+ UnusedModelDefinition: "BT121_UNUSED_MODEL_DEFINITION",
34995
+ DuplicateModelDefinition: "BT122_DUPLICATE_MODEL_DEFINITION",
34934
34996
  ExternalModelFileNotFound: "BT321_EXTERNAL_MODEL_FILE_NOT_FOUND",
34935
34997
  AugmentationFileNotFound: "BT324_AUGMENTATION_FILE_NOT_FOUND",
34936
34998
  MissingTreeNodesModel: "BT322_MISSING_TREENODESMODEL",
@@ -35174,6 +35236,21 @@ var RULES = {
35174
35236
  defaultSeverity: "error",
35175
35237
  description: "Node model definitions must agree on kind and port shape."
35176
35238
  },
35239
+ "model/no-conflicting-kind-for-id": {
35240
+ code: RuleCodes.ConflictingModelKind,
35241
+ defaultSeverity: "error",
35242
+ description: "A model ID must not be defined with different kinds."
35243
+ },
35244
+ "model/no-unused-definition": {
35245
+ code: RuleCodes.UnusedModelDefinition,
35246
+ defaultSeverity: "error",
35247
+ description: "Inline model definitions should be used in the same BT XML file."
35248
+ },
35249
+ "model/no-duplicate-definition": {
35250
+ code: RuleCodes.DuplicateModelDefinition,
35251
+ defaultSeverity: "error",
35252
+ description: "A user-defined model (ID, kind) should be defined only once."
35253
+ },
35177
35254
  "suppression/no-unused": {
35178
35255
  code: RuleCodes.UnusedSuppression,
35179
35256
  defaultSeverity: "warn",
@@ -36003,6 +36080,18 @@ function getSubTreeReferences(index, id) {
36003
36080
  (model) => model.subtreeReferences.filter((reference) => reference.id === id)
36004
36081
  );
36005
36082
  }
36083
+ function getAllNodeUsages(index) {
36084
+ return getAllDocumentModels(index).flatMap((model) => model.nodeUsages);
36085
+ }
36086
+ function getNodeUsagesByUri(index) {
36087
+ const grouped = /* @__PURE__ */ new Map();
36088
+ for (const usage of getAllNodeUsages(index)) {
36089
+ const list = grouped.get(usage.uri) ?? [];
36090
+ list.push(usage);
36091
+ grouped.set(usage.uri, list);
36092
+ }
36093
+ return grouped;
36094
+ }
36006
36095
  function getBehaviorTreeIds(index) {
36007
36096
  return [...asSemanticIndexState2(index).behaviorTreesById.keys()];
36008
36097
  }
@@ -36793,6 +36882,50 @@ function findSemanticPortBindingAtPosition(view, position) {
36793
36882
  return void 0;
36794
36883
  }
36795
36884
 
36885
+ // ../semantic/src/model-definition-facts.ts
36886
+ function modelDefinitionKeyString(key) {
36887
+ return `${key.kind}\0${key.id}`;
36888
+ }
36889
+ function getModelDefinitionFacts(index) {
36890
+ return getAllNodeModelDefinitions(index).map((model) => {
36891
+ const sourceKind = model.source ?? model.sourceMeta?.sourceKind;
36892
+ return {
36893
+ key: {
36894
+ id: model.id,
36895
+ kind: model.kind
36896
+ },
36897
+ model,
36898
+ id: model.id,
36899
+ kind: model.kind,
36900
+ uri: model.uri,
36901
+ range: model.range,
36902
+ sourceKind,
36903
+ isBuiltin: sourceKind === "builtin",
36904
+ isCanonicalModelFile: sourceKind === "external-tree-nodes-model",
36905
+ editable: model.editable !== false
36906
+ };
36907
+ });
36908
+ }
36909
+ function groupModelDefinitionsById(facts) {
36910
+ const grouped = /* @__PURE__ */ new Map();
36911
+ for (const fact of facts) {
36912
+ const list = grouped.get(fact.id) ?? [];
36913
+ list.push(fact);
36914
+ grouped.set(fact.id, list);
36915
+ }
36916
+ return grouped;
36917
+ }
36918
+ function groupModelDefinitionsByKey(facts) {
36919
+ const grouped = /* @__PURE__ */ new Map();
36920
+ for (const fact of facts) {
36921
+ const key = modelDefinitionKeyString(fact.key);
36922
+ const list = grouped.get(key) ?? [];
36923
+ list.push(fact);
36924
+ grouped.set(key, list);
36925
+ }
36926
+ return grouped;
36927
+ }
36928
+
36796
36929
  // ../syntax/src/parse/document-kind.ts
36797
36930
  function finalizeDocumentKind(input) {
36798
36931
  const { document, diagnostics, options, partial: partial2 } = input;
@@ -39060,6 +39193,192 @@ async function resolveIncludeGraph(input) {
39060
39193
  };
39061
39194
  }
39062
39195
 
39196
+ // ../project/src/model-conventions.ts
39197
+ var RULE_CONFLICTING_KIND = "model/no-conflicting-kind-for-id";
39198
+ var RULE_UNUSED_DEFINITION = "model/no-unused-definition";
39199
+ var RULE_DUPLICATE_DEFINITION = "model/no-duplicate-definition";
39200
+ function createConventionDiagnostic(input) {
39201
+ const diagnostic = createDiagnostic(
39202
+ input.code,
39203
+ input.severity,
39204
+ input.message,
39205
+ input.range,
39206
+ input.uri,
39207
+ void 0,
39208
+ input.data
39209
+ );
39210
+ return {
39211
+ ...diagnostic,
39212
+ rule: input.rule,
39213
+ ...input.relatedInformation ? { relatedInformation: input.relatedInformation } : {}
39214
+ };
39215
+ }
39216
+ function resolveConventionSeverity(config2, rule) {
39217
+ const severity = getRuleSeverity(config2.linter.rules, rule);
39218
+ if (severity === "off") return void 0;
39219
+ if (severity === "info") return DiagnosticSeverity.Info;
39220
+ if (severity === "warn") return DiagnosticSeverity.Warning;
39221
+ return DiagnosticSeverity.Error;
39222
+ }
39223
+ function definitionRange(definition) {
39224
+ return definition.model.idRange ?? definition.range;
39225
+ }
39226
+ function definitionInfo(definition) {
39227
+ return {
39228
+ uri: definition.uri,
39229
+ sourceKind: definition.sourceKind,
39230
+ modelKind: definition.kind,
39231
+ range: definition.range
39232
+ };
39233
+ }
39234
+ function validateConflictingKinds(input) {
39235
+ const severity = resolveConventionSeverity(input.config, RULE_CONFLICTING_KIND);
39236
+ if (!severity) return [];
39237
+ const diagnostics = [];
39238
+ const groupedById = groupModelDefinitionsById(input.facts.filter((fact) => !fact.isBuiltin));
39239
+ for (const [id, definitions] of groupedById) {
39240
+ const kinds = new Set(definitions.map((definition) => definition.kind));
39241
+ if (kinds.size <= 1) continue;
39242
+ const primary = definitions[0];
39243
+ if (!primary) continue;
39244
+ for (const definition of definitions.slice(1)) {
39245
+ if (definition.kind === primary.kind) continue;
39246
+ const primaryRange = definitionRange(primary);
39247
+ const relatedInformation = primary.uri && primaryRange ? [
39248
+ {
39249
+ uri: primary.uri,
39250
+ range: primaryRange,
39251
+ message: "first conflicting definition"
39252
+ }
39253
+ ] : void 0;
39254
+ diagnostics.push(
39255
+ createConventionDiagnostic({
39256
+ code: "BT120_CONFLICTING_MODEL_KIND",
39257
+ message: `model ID \`${id}\` has conflicting kinds (\`${primary.kind}\` vs \`${definition.kind}\`)`,
39258
+ uri: definition.uri,
39259
+ range: definitionRange(definition),
39260
+ rule: RULE_CONFLICTING_KIND,
39261
+ severity,
39262
+ data: {
39263
+ kind: "conflicting-model-kind",
39264
+ nodeId: id,
39265
+ definitions: definitions.map(definitionInfo)
39266
+ },
39267
+ relatedInformation
39268
+ })
39269
+ );
39270
+ }
39271
+ }
39272
+ return diagnostics;
39273
+ }
39274
+ function validateUsedOnly(input) {
39275
+ if (input.config.models.convention !== "used-only") return [];
39276
+ const severity = resolveConventionSeverity(input.config, RULE_UNUSED_DEFINITION);
39277
+ if (!severity) return [];
39278
+ const diagnostics = [];
39279
+ const usagesByUri = getNodeUsagesByUri(input.index);
39280
+ for (const definition of input.facts) {
39281
+ if (definition.isBuiltin) continue;
39282
+ if (definition.sourceKind !== "inline-tree-nodes-model") continue;
39283
+ if (definition.kind === "SubTree") continue;
39284
+ const sameFileNodeUsages = usagesByUri.get(definition.uri ?? "") ?? [];
39285
+ const usedNodeIds = new Set(
39286
+ sameFileNodeUsages.filter((usage) => usage.kind === "node").map((usage) => usage.id)
39287
+ );
39288
+ if (usedNodeIds.has(definition.id)) continue;
39289
+ diagnostics.push(
39290
+ createConventionDiagnostic({
39291
+ code: "BT121_UNUSED_MODEL_DEFINITION",
39292
+ message: `unused inline model definition \`${definition.id}\` in this file`,
39293
+ uri: definition.uri,
39294
+ range: definitionRange(definition),
39295
+ rule: RULE_UNUSED_DEFINITION,
39296
+ severity,
39297
+ data: {
39298
+ kind: "unused-model-definition",
39299
+ nodeId: definition.id,
39300
+ modelKind: definition.kind,
39301
+ sourceKind: "inline-tree-nodes-model",
39302
+ fix: definition.uri && definitionRange(definition) && definition.editable ? {
39303
+ kind: "delete-definition",
39304
+ uri: definition.uri,
39305
+ range: definitionRange(definition)
39306
+ } : void 0
39307
+ }
39308
+ })
39309
+ );
39310
+ }
39311
+ return diagnostics;
39312
+ }
39313
+ function duplicateFix(definitions) {
39314
+ const canonical = definitions.filter((definition) => definition.isCanonicalModelFile);
39315
+ if (canonical.length !== 1) return void 0;
39316
+ const keep = canonical[0];
39317
+ if (!keep?.uri) return void 0;
39318
+ const deleteTargets = definitions.filter((definition) => definition !== keep);
39319
+ if (deleteTargets.some(
39320
+ (definition) => !definition.uri || !definitionRange(definition) || !definition.editable
39321
+ )) {
39322
+ return void 0;
39323
+ }
39324
+ return {
39325
+ kind: "delete-non-canonical-definitions",
39326
+ keep: {
39327
+ uri: keep.uri,
39328
+ range: definitionRange(keep)
39329
+ },
39330
+ delete: deleteTargets.map((definition) => ({
39331
+ uri: definition.uri,
39332
+ range: definitionRange(definition)
39333
+ }))
39334
+ };
39335
+ }
39336
+ function validateSingleSource(input) {
39337
+ if (input.config.models.convention !== "single-source") return [];
39338
+ const severity = resolveConventionSeverity(input.config, RULE_DUPLICATE_DEFINITION);
39339
+ if (!severity) return [];
39340
+ const diagnostics = [];
39341
+ const groupedByKey = groupModelDefinitionsByKey(input.facts.filter((fact) => !fact.isBuiltin));
39342
+ for (const definitions of groupedByKey.values()) {
39343
+ if (definitions.length <= 1) continue;
39344
+ const primary = definitions[0];
39345
+ if (!primary) continue;
39346
+ diagnostics.push(
39347
+ createConventionDiagnostic({
39348
+ code: "BT122_DUPLICATE_MODEL_DEFINITION",
39349
+ message: `duplicate model definition for \`${primary.id}\` (${primary.kind})`,
39350
+ uri: primary.uri,
39351
+ range: definitionRange(primary),
39352
+ rule: RULE_DUPLICATE_DEFINITION,
39353
+ severity,
39354
+ data: {
39355
+ kind: "duplicate-model-definition",
39356
+ nodeId: primary.id,
39357
+ modelKind: primary.kind,
39358
+ definitions: definitions.map((definition) => ({
39359
+ uri: definition.uri,
39360
+ sourceKind: definition.sourceKind,
39361
+ range: definition.range,
39362
+ canonical: definition.isCanonicalModelFile,
39363
+ editable: definition.editable
39364
+ })),
39365
+ fix: duplicateFix(definitions)
39366
+ }
39367
+ })
39368
+ );
39369
+ }
39370
+ return diagnostics;
39371
+ }
39372
+ function validateModelConventions(input) {
39373
+ if (input.config.linter.enabled === false) return [];
39374
+ const facts = getModelDefinitionFacts(input.index);
39375
+ return [
39376
+ ...validateConflictingKinds({ config: input.config, facts }),
39377
+ ...validateUsedOnly({ config: input.config, index: input.index, facts }),
39378
+ ...validateSingleSource({ config: input.config, facts })
39379
+ ];
39380
+ }
39381
+
39063
39382
  // ../project/src/node-definitions.ts
39064
39383
  function createPositionAt2(text) {
39065
39384
  const lineStarts = [0];
@@ -39257,7 +39576,10 @@ async function buildProjectIndex(input) {
39257
39576
  }) : void 0;
39258
39577
  const externalDocs = input.externalModelDocuments;
39259
39578
  const augmentations = input.augmentations;
39260
- const nodeDefinitions = await loadProjectNodeModels({ project: input.project, host: input.host });
39579
+ const nodeDefinitions = await loadProjectNodeModels({
39580
+ project: input.project,
39581
+ host: input.host
39582
+ });
39261
39583
  diagnostics.push(...nodeDefinitions.diagnostics);
39262
39584
  const nodeDefinitionModels = nodeDefinitions.nodeModels;
39263
39585
  const activeDocs = graphResult && (mode === "entrypoints" || input.resolveGraph) ? uniqueDocuments([
@@ -39279,7 +39601,13 @@ async function buildProjectIndex(input) {
39279
39601
  includeIssuesByUri: groupIncludeIssues(graphResult?.issues ?? []),
39280
39602
  suppressionIssuesByUri: /* @__PURE__ */ new Map()
39281
39603
  };
39282
- diagnostics.push(...semanticResult.diagnostics);
39604
+ diagnostics.push(
39605
+ ...semanticResult.diagnostics,
39606
+ ...validateModelConventions({
39607
+ config: resolvedConfig,
39608
+ index: semanticResult.index
39609
+ })
39610
+ );
39283
39611
  const reachableBehaviorTreesById = new Map(
39284
39612
  getBehaviorTreeIds(semanticResult.index).map((id) => [
39285
39613
  id,
@@ -46524,7 +46852,7 @@ function handleGetChildCapability(workspace, params) {
46524
46852
  }
46525
46853
 
46526
46854
  // src/server.ts
46527
- var SERVER_VERSION = true ? "0.1.2" : "unknown";
46855
+ var SERVER_VERSION = true ? "0.1.3" : "unknown";
46528
46856
  var connection = (0, import_node4.createConnection)(import_node4.ProposedFeatures.all);
46529
46857
  var documents = new import_node4.TextDocuments(TextDocument);
46530
46858
  var openUris = /* @__PURE__ */ new Set();