@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
1,027 lines • 89.6 kB
JavaScript
"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResourceConsumptionConstraint = void 0;
const Project_1 = require("./../app/Project");
const ProjectInfoItem_1 = __importDefault(require("./ProjectInfoItem"));
const IInfoItemData_1 = require("./IInfoItemData");
const IProjectInfoData_1 = require("./IProjectInfoData");
const Constants_1 = require("../core/Constants");
const Utilities_1 = __importDefault(require("../core/Utilities"));
const Log_1 = __importDefault(require("../core/Log"));
const Status_1 = require("../app/Status");
const GeneratorRegistrations_1 = __importStar(require("./registration/GeneratorRegistrations"));
const StorageUtilities_1 = __importDefault(require("../storage/StorageUtilities"));
const ContentIndex_1 = __importDefault(require("../core/ContentIndex"));
const MinecraftUtilities_1 = __importDefault(require("../minecraft/MinecraftUtilities"));
const SummaryInfoGenerator_1 = __importDefault(require("./SummaryInfoGenerator"));
const HashUtilities_1 = __importDefault(require("../core/HashUtilities"));
const Database_1 = __importDefault(require("../minecraft/Database"));
const Telemetry_1 = __importDefault(require("../analytics/Telemetry"));
const TelemetryConstants_1 = require("../analytics/TelemetryConstants");
const InfoGeneratorTopicUtilities_1 = __importDefault(require("./InfoGeneratorTopicUtilities"));
const CrossReferenceIndexGenerator_1 = __importDefault(require("./CrossReferenceIndexGenerator"));
const TypesInfoGenerator_1 = __importDefault(require("./TypesInfoGenerator"));
const ItemBatchSize = 500;
/**
* Controls how aggressively generators should constrain resource consumption.
* Use this to balance between thoroughness and performance/memory usage.
*/
var ResourceConsumptionConstraint;
(function (ResourceConsumptionConstraint) {
/** No constraints - process all data regardless of resource usage */
ResourceConsumptionConstraint[ResourceConsumptionConstraint["none"] = 0] = "none";
/** Medium constraints - apply reasonable limits to prevent excessive resource usage */
ResourceConsumptionConstraint[ResourceConsumptionConstraint["medium"] = 5] = "medium";
})(ResourceConsumptionConstraint || (exports.ResourceConsumptionConstraint = ResourceConsumptionConstraint = {}));
class ProjectInfoSet {
project;
suite;
info;
items = [];
itemsByStoragePath = {};
contentIndex;
performAggressiveCleanup = false;
constrainResourceConsumption = ResourceConsumptionConstraint.medium;
static _generatorsById = {};
_isGenerating = false;
_completedGeneration = false;
_excludeTests;
_pendingGenerateRequests = [];
static CommonCsvHeader = "Test,TestId,Type,Item,Message,Data,Path";
static getSuiteFromString(suiteName) {
switch (suiteName.toLowerCase()) {
case "addon":
case "addons":
return IProjectInfoData_1.ProjectInfoSuite.cooperativeAddOn;
case "sharing":
return IProjectInfoData_1.ProjectInfoSuite.sharing;
case "sharingstrict":
return IProjectInfoData_1.ProjectInfoSuite.sharingStrict;
case "currentplatform":
return IProjectInfoData_1.ProjectInfoSuite.currentPlatformVersions;
default:
return IProjectInfoData_1.ProjectInfoSuite.defaultInDevelopment; // default is all infogenerators except cooperative add-on and sharing
}
}
static getSuiteString(suite) {
switch (suite) {
case IProjectInfoData_1.ProjectInfoSuite.sharing:
return "sharing";
case IProjectInfoData_1.ProjectInfoSuite.sharingStrict:
return "sharingstrict";
case IProjectInfoData_1.ProjectInfoSuite.cooperativeAddOn:
return "addon";
case IProjectInfoData_1.ProjectInfoSuite.currentPlatformVersions:
return "currentplatform";
default:
return "all";
}
}
get completedGeneration() {
return this._completedGeneration;
}
/**
* Mark generation as complete. Used when results are provided externally
* (e.g., from a web worker).
*/
async markGenerationCompleteAsync() {
this._completedGeneration = true;
this._isGenerating = false;
// Build the itemsByStoragePath index from items array
this._rebuildItemsByStoragePath();
// Aggregate features from info items (needed for summary display)
this.aggregateFeatures();
// Build contentIndex from project files.
// The web worker path doesn't populate the contentIndex (it only streams
// validation items), so we must build it on the main thread.
this._buildContentIndexFromProject();
// Populate cross-reference and type annotations in the contentIndex.
// These are normally populated by generators running in the worker,
// but the worker's ContentIndex is not transferred back.
await this._populateContentIndexAnnotationsAsync();
}
/**
* Runs lightweight annotation-producing generators against the contentIndex.
* This populates cross-reference annotations (geometry IDs, animation names,
* entity/block/item types, etc.) that power autocomplete suggestions.
*
* Called after _buildContentIndexFromProject() in the web worker path,
* since the worker's ContentIndex (with all annotations) is not transferred back.
* The project files are already loaded in memory, so this runs quickly.
*/
async _populateContentIndexAnnotationsAsync() {
if (!this.project || !this.contentIndex) {
return;
}
// CrossReferenceIndexGenerator: geometry, animation, animation controller,
// render controller, particle, fog, sound, loot table, recipe, biome,
// spawn rule, dialogue, function, structure annotations
const crossRefGen = new CrossReferenceIndexGenerator_1.default();
await crossRefGen.generate(this.project, this.contentIndex);
// TypesInfoGenerator: entity, block, item, feature type annotations
const typesGen = new TypesInfoGenerator_1.default();
await typesGen.generate(this.project, this.contentIndex);
// Note: generate() returns info items which we discard here —
// we already have the info items from the worker stream.
}
/**
* Populates the contentIndex by scanning all project items and their files.
* This is needed when the info set was generated via a web worker, which
* doesn't build or transfer the content index.
*/
_buildContentIndexFromProject() {
if (!this.project) {
return;
}
const ci = new ContentIndex_1.default();
ci.iteration = new Date().getTime();
for (const projectItem of this.project.items) {
if (!projectItem.projectPath) {
continue;
}
const file = projectItem.defaultFile;
if (file) {
const fileName = file.name;
ci.insert(StorageUtilities_1.default.getBaseFromName(fileName), projectItem.projectPath);
ci.insert(file.storageRelativePath, projectItem.projectPath);
if (file.content && typeof file.content === "string") {
const fileExtension = StorageUtilities_1.default.getTypeFromName(fileName);
switch (fileExtension) {
case "json":
ci.parseJsonContent(projectItem.projectPath, file.content);
break;
case "ts":
case "js":
case "mjs":
ci.parseJsContent(projectItem.projectPath, file.content);
break;
}
}
}
}
this.contentIndex = ci;
}
/**
* Rebuilds the itemsByStoragePath index from the items array.
* This is needed when items are set externally (e.g., from a web worker).
*/
_rebuildItemsByStoragePath() {
this.itemsByStoragePath = {};
for (const item of this.items) {
if (item.projectItem) {
const path = item.projectItem.projectPath;
if (path && typeof path === "string") {
if (!this.itemsByStoragePath[path]) {
this.itemsByStoragePath[path] = [];
}
this.itemsByStoragePath[path]?.push(item);
}
}
}
}
get errorAndFailCount() {
let count = 0;
for (const item of this.items) {
if (item.itemType === IInfoItemData_1.InfoItemType.error ||
item.itemType === IInfoItemData_1.InfoItemType.internalProcessingError ||
item.itemType === IInfoItemData_1.InfoItemType.testCompleteFail) {
count++;
}
}
return count;
}
get errorFailWarnCount() {
let count = 0;
for (const item of this.items) {
if (item.itemType === IInfoItemData_1.InfoItemType.error ||
item.itemType === IInfoItemData_1.InfoItemType.warning ||
item.itemType === IInfoItemData_1.InfoItemType.internalProcessingError ||
item.itemType === IInfoItemData_1.InfoItemType.testCompleteFail) {
count++;
}
}
return count;
}
get errorFailWarnString() {
let str = [];
for (const item of this.items) {
if (item.itemType === IInfoItemData_1.InfoItemType.error ||
item.itemType === IInfoItemData_1.InfoItemType.warning ||
item.itemType === IInfoItemData_1.InfoItemType.internalProcessingError ||
item.itemType === IInfoItemData_1.InfoItemType.testCompleteFail) {
str.push(item.toString());
}
}
return str.join("\n");
}
constructor(project, suite, excludeTests, info, items, index, performAggressiveCleanup) {
this.project = project;
this.info = info ? info : {};
this.contentIndex = index ? index : new ContentIndex_1.default();
this.performAggressiveCleanup = performAggressiveCleanup ?? false;
if (items) {
for (const item of items) {
let projectItem = undefined;
if (item.p) {
if (project) {
projectItem = project.getItemByProjectPath(item.p);
}
if (!this.itemsByStoragePath[item.p]) {
this.itemsByStoragePath[item.p] = [];
}
let projectInfoItem = new ProjectInfoItem_1.default(item.iTp, item.gId, item.gIx, item.m, projectItem, item.d, item.iId, item.c, item.p);
this.itemsByStoragePath[item.p]?.push(projectInfoItem);
this.items.push(projectInfoItem);
}
else {
this.items.push(new ProjectInfoItem_1.default(item.iTp, item.gId, item.gIx, item.m, projectItem, item.d, item.iId, item.c));
}
}
}
if (suite) {
this.suite = suite;
}
else {
this.suite = IProjectInfoData_1.ProjectInfoSuite.defaultInDevelopment;
}
if (index) {
if (info) {
for (const key in info) {
const val = info[key];
if (val && typeof val === "string") {
if (ProjectInfoSet.isAggregableFieldName(key)) {
index.parseTextContent("inspector", val);
}
}
}
}
}
if (excludeTests) {
const excludeTestList = [];
for (const excludeTest of excludeTests) {
const vals = excludeTest.trim().split(",");
for (const val of vals) {
const valD = val.toUpperCase().trim();
if (valD.length > 0) {
excludeTestList.push(valD);
}
}
}
this._excludeTests = excludeTestList;
}
}
static getTopicData(id, index) {
if (!Utilities_1.default.isUsableAsObjectKey(id)) {
Log_1.default.unsupportedToken(id);
throw new Error();
}
// First, try to get topic data from form.json file (synchronously if already loaded)
const formTopicData = InfoGeneratorTopicUtilities_1.default.getTopicDataSync(id, index);
if (formTopicData) {
return formTopicData;
}
// Fall back to generator's getTopicData method if available
const gen = ProjectInfoSet._generatorsById[id];
if (gen && typeof gen.getTopicData === "function") {
return gen.getTopicData(index);
}
for (const gen of GeneratorRegistrations_1.default.projectGenerators) {
if (gen.id === id) {
this._generatorsById[id] = gen;
if (typeof gen.getTopicData === "function") {
return gen.getTopicData(index);
}
return undefined;
}
}
return undefined;
}
static _getLineLocationFromIndex(content, index) {
const lineNumber = content.substring(0, index).split("\n").length;
const lastLineBreak = content.lastIndexOf("\n", index - 1);
const column = index - (lastLineBreak + 1) + 1;
return { lineNumber, column };
}
static async findLineLocationForItem(content, item) {
if (!content) {
return undefined;
}
const directMatch = (matchText) => {
if (!matchText) {
return undefined;
}
const index = content.indexOf(matchText);
if (index >= 0) {
return ProjectInfoSet._getLineLocationFromIndex(content, index);
}
return undefined;
};
const contentLocation = directMatch(item.content);
if (contentLocation) {
return contentLocation;
}
if (typeof item.data === "string") {
const normalizedData = item.data.startsWith("Relevant line: ")
? item.data.substring("Relevant line: ".length)
: item.data;
const dataLocation = directMatch(normalizedData);
if (dataLocation) {
return dataLocation;
}
}
if (item.generatorId) {
try {
const topicData = await InfoGeneratorTopicUtilities_1.default.getTopicData(item.generatorId, item.generatorIndex);
if (topicData?.suggestedLineToken) {
const token = topicData.suggestedLineToken;
const escapedToken = token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
if (topicData.suggestedLineShouldHaveData && item.data) {
const dataStr = String(item.data);
const escapedData = dataStr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const combinedPattern = new RegExp(`"${escapedToken}"[^\\n]*${escapedData}|${escapedData}[^\\n]*"${escapedToken}"`, "i");
const combinedMatch = content.match(combinedPattern);
if (combinedMatch && combinedMatch.index !== undefined) {
return ProjectInfoSet._getLineLocationFromIndex(content, combinedMatch.index);
}
}
const tokenPattern = new RegExp(`"${escapedToken}"\\s*:\\s*[^,}\\]]+`, "i");
const match = content.match(tokenPattern);
if (match && match.index !== undefined) {
return ProjectInfoSet._getLineLocationFromIndex(content, match.index);
}
const simpleIndex = content.indexOf(`"${token}"`);
if (simpleIndex >= 0) {
return ProjectInfoSet._getLineLocationFromIndex(content, simpleIndex);
}
}
}
catch {
// Ignore errors loading topic data
}
}
if (item.generatorId === "FORMATVER" || item.generatorId?.includes("FORMAT")) {
const versionPattern = /"format_version"\s*:\s*"[^"]+"/i;
const match = content.match(versionPattern);
if (match && match.index !== undefined) {
return ProjectInfoSet._getLineLocationFromIndex(content, match.index);
}
}
if (item.generatorId === "ENTITYTYPE" || item.generatorId === "BLOCKTYPE" || item.generatorId === "ITEMTYPE") {
const identifierPattern = /"identifier"\s*:\s*"[^"]+"/;
const match = content.match(identifierPattern);
if (match && match.index !== undefined) {
return ProjectInfoSet._getLineLocationFromIndex(content, match.index);
}
}
return undefined;
}
getCountByType(itemType) {
let count = 0;
for (const item of this.items) {
if (item.itemType === itemType) {
count++;
}
}
return count;
}
getSummaryByType(itemType) {
let str = [];
for (const item of this.items) {
if (str.length < 15 && item.itemType === itemType) {
str.push(item.toString());
}
}
return str.join("\n");
}
matchesSuite(generator) {
if (this.suite === IProjectInfoData_1.ProjectInfoSuite.defaultInDevelopment &&
!GeneratorRegistrations_1.TestsToExcludeFromDefaultSuite.includes(generator.id)) {
return true;
}
if (this.suite === IProjectInfoData_1.ProjectInfoSuite.sharing || this.suite === IProjectInfoData_1.ProjectInfoSuite.sharingStrict) {
if (generator.id === "SHARING") {
return true;
}
}
if (this.suite === IProjectInfoData_1.ProjectInfoSuite.sharingStrict) {
if (generator.id === "LANGFILES" || generator.id === "VANDUPES") {
return true;
}
}
if (this.suite === IProjectInfoData_1.ProjectInfoSuite.currentPlatformVersions) {
if (generator.id === "MINENGINEVER" ||
generator.id === "BASEGAMEVER" ||
generator.id === "FORMATVER" ||
generator.id === "WORLDDATA" ||
generator.id === "CHKMANIF") {
return true;
}
}
if (this.suite === IProjectInfoData_1.ProjectInfoSuite.cooperativeAddOn) {
if (generator.id.indexOf("CADDON") >= 0 ||
generator.id === "PACKSIZE" ||
generator.id === "STRICT" ||
generator.id === "TEXTURE" ||
generator.id === "MINENGINEVER" ||
generator.id === "WORLDDATA") {
return true;
}
}
return false;
}
/**
* Generate info items for the project.
* @param force If true, regenerate even if already completed.
* @param skipRelationsProcessing If true, skip the processRelations call (useful when relations
* have already been processed, e.g., in a combined worker operation).
* @param onProgress Optional callback for progress updates (useful for worker thread communication).
*/
async generateForProject(force, skipRelationsProcessing, onProgress) {
if (force === true && this._completedGeneration) {
this._completedGeneration = false;
this._isGenerating = false;
}
if (this._completedGeneration) {
return;
}
if (!skipRelationsProcessing) {
await this.project?.processRelations();
}
if (this._isGenerating) {
const pendingGenerate = this._pendingGenerateRequests;
const prom = (resolve, reject) => {
pendingGenerate.push(resolve);
};
await new Promise(prom);
}
else {
this._isGenerating = true;
const generationStartTime = Date.now();
if (!this.project) {
Log_1.default.throwUnexpectedUndefined("PISGFP");
return;
}
let baseValidationMessage = "Validating '" + this.project.simplifiedName + "'";
const valOperId = await this.project?.creatorTools.notifyOperationStarted(baseValidationMessage + " (0%)", Status_1.StatusTopic.validation);
this.info.summary = undefined;
const projGenerators = GeneratorRegistrations_1.default.projectGenerators;
const itemGenerators = GeneratorRegistrations_1.default.itemGenerators;
const fileGenerators = GeneratorRegistrations_1.default.fileGenerators;
const genItems = [];
const genItemsByStoragePath = {};
const genContentIndex = new ContentIndex_1.default();
genContentIndex.iteration = new Date().getTime();
await this.project.loc.load();
await Database_1.default.loadVanillaCatalog();
// Preload all topic forms for generators so sync lookups work
const allGeneratorIds = [
...projGenerators.map((g) => g.id),
...itemGenerators.map((g) => g.id),
...fileGenerators.map((g) => g.id),
];
await InfoGeneratorTopicUtilities_1.default.preloadAllForms(allGeneratorIds);
if (this.project?.errorState === Project_1.ProjectErrorState.cabinetFileCouldNotBeProcessed) {
genItems.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.internalProcessingError, "PROJECTMETA", 500, this.project.simplifiedName + ": " + this.project.errorMessage));
}
else {
const projectFolder = await this.project.ensureProjectFolder();
const generatorTimings = [];
const preProcessStart = Date.now();
await this.preProcessFolder(this.project, projectFolder, genItems, genItemsByStoragePath, genContentIndex, fileGenerators, 0);
generatorTimings.push({
phase: "preprocess",
id: "preProcessFolder",
durationMs: Date.now() - preProcessStart,
});
for (let i = 0; i < projGenerators.length; i++) {
const gen = projGenerators[i];
if ((!this._excludeTests || !this._excludeTests.includes(gen.id)) && gen && this.matchesSuite(gen)) {
const projGenPercent = Math.floor(10 + (i / projGenerators.length) * 20); // 10-30%
await this.project?.creatorTools.notifyOperationUpdate(valOperId, baseValidationMessage + " - " + gen.title + " (" + projGenPercent + "%)", Status_1.StatusTopic.validation);
// Send progress to worker callback if provided
if (onProgress) {
const percent = Math.floor(10 + (i / projGenerators.length) * 20); // 10-30%
onProgress(`Validating: ${gen.title}`, percent);
}
GeneratorRegistrations_1.default.configureForSuite(gen, this.suite);
try {
const genStart = Date.now();
const results = await gen.generate(this.project, genContentIndex);
generatorTimings.push({ phase: "project", id: gen.id, durationMs: Date.now() - genStart });
for (const item of results) {
this.pushItem(genItems, genItemsByStoragePath, item);
}
}
catch (e) {
// V--- add a breakpoint to the line below to catch validator exceptions (1 of 3) ---V
genItems.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.internalProcessingError, gen.id, 500, this.project.simplifiedName + ": " + e.message + (e.stack ? " (" + e.stack + ")" : "")));
if (e && (!e.message || !e.message.indexOf || e.message.indexOf("etwork ") < 0)) {
Log_1.default.debugAlert(e);
}
else {
this.project?.creatorTools.notifyStatusUpdate("Could not connect to network to retrieve resources for validation. Details: " + e.toString());
}
}
}
}
const itemsCopy = this.project.getItemsCopy();
const itemGenTimings = {};
for (const gen of itemGenerators) {
itemGenTimings[gen.id] = 0;
}
const itemLoopStart = Date.now();
for (let i = 0; i < itemsCopy.length; i++) {
const pi = itemsCopy[i];
if (i % ItemBatchSize === ItemBatchSize - 1) {
const itemPercent = Math.floor(30 + (i / itemsCopy.length) * 50); // 30-80%
await this.project?.creatorTools.notifyOperationUpdate(valOperId, baseValidationMessage + " - items (" + itemPercent + "%)", Status_1.StatusTopic.validation);
// Send progress to worker callback if provided
if (onProgress) {
onProgress(`Validating items`, itemPercent);
}
}
if (!pi.isContentLoaded) {
await pi.loadContent();
}
for (let j = 0; j < itemGenerators.length; j++) {
const gen = itemGenerators[j];
if ((!this._excludeTests || !this._excludeTests.includes(gen.id)) && this.matchesSuite(gen)) {
GeneratorRegistrations_1.default.configureForSuite(gen, this.suite);
try {
const itemGenStart = Date.now();
const results = await gen.generate(pi, genContentIndex, {
performAggressiveCleanup: this.performAggressiveCleanup,
constrainResourceConsumption: this.constrainResourceConsumption,
onProgress: onProgress,
});
itemGenTimings[gen.id] += Date.now() - itemGenStart;
for (const item of results) {
this.pushItem(genItems, genItemsByStoragePath, item);
}
}
catch (e) {
// V--- add a breakpoint to the line below to catch validator exceptions (2 of 3) ---V
genItems.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.internalProcessingError, gen.id, 501, "IP2:" + this.project.simplifiedName + ": " + e.toString()));
}
}
}
}
generatorTimings.push({ phase: "items-loop", id: "allItemGenerators", durationMs: Date.now() - itemLoopStart });
for (const gen of itemGenerators) {
if (itemGenTimings[gen.id] > 0) {
generatorTimings.push({ phase: "item", id: gen.id, durationMs: itemGenTimings[gen.id] });
}
}
const processFolderStart = Date.now();
await this.processFolder(this.project, projectFolder, genItems, genItemsByStoragePath, genContentIndex, fileGenerators, 0);
generatorTimings.push({
phase: "postprocess",
id: "processFolder",
durationMs: Date.now() - processFolderStart,
});
// Log timing breakdown
generatorTimings.sort((a, b) => b.durationMs - a.durationMs);
const totalValidationMs = Date.now() - generationStartTime;
Log_1.default.verbose(`[Validation] Total validation time: ${totalValidationMs}ms`);
Log_1.default.verbose(`[Validation] Top generators by time:`);
for (const t of generatorTimings.slice(0, 20)) {
Log_1.default.verbose(`[Validation] ${t.phase.padEnd(12)} ${t.id.padEnd(45)} ${t.durationMs}ms`);
}
}
await this.project?.creatorTools.notifyOperationUpdate(valOperId, baseValidationMessage + " - finishing (95%)", Status_1.StatusTopic.validation);
this.addTestSummations(genItems, genItemsByStoragePath, projGenerators, this._excludeTests);
this.addTestSummations(genItems, genItemsByStoragePath, itemGenerators, this._excludeTests);
this.addTestSummations(genItems, genItemsByStoragePath, fileGenerators, this._excludeTests);
genItems.sort((a, b) => {
if (a.generatorId !== b.generatorId) {
return Utilities_1.default.staticCompare(a.generatorId, b.generatorId);
}
if (a.generatorIndex !== b.generatorIndex) {
return a.generatorIndex - b.generatorIndex;
}
const aPath = a.projectItemPath;
const bPath = b.projectItemPath;
if (aPath !== bPath && aPath && bPath) {
return Utilities_1.default.staticCompare(aPath, bPath);
}
if (aPath !== bPath && aPath) {
return 1;
}
if (aPath !== bPath && bPath) {
return -1;
}
if (a.message !== b.message && a.message && b.message) {
return Utilities_1.default.staticCompare(a.message, b.message);
}
if (a.message !== b.message && a.message) {
return 1;
}
if (a.message !== b.message && b.message) {
return -1;
}
if (a.data !== b.data && a.data && b.data && typeof b.data === "string" && typeof a.data === "string") {
return Utilities_1.default.staticCompare(a.data, b.data);
}
if (a.data !== b.data && a.data) {
return 1;
}
if (a.data !== b.data && b.data) {
return -1;
}
return 0;
});
this.items = genItems;
this.itemsByStoragePath = genItemsByStoragePath;
this.contentIndex = genContentIndex;
this._completedGeneration = true;
this.generateProjectMetaInfo();
const pendingLoad = this._pendingGenerateRequests;
this._pendingGenerateRequests = [];
this.info.errorCount = this.getCountByType(IInfoItemData_1.InfoItemType.error);
this.info.internalProcessingErrorCount = this.getCountByType(IInfoItemData_1.InfoItemType.internalProcessingError);
this.info.warningCount = this.getCountByType(IInfoItemData_1.InfoItemType.warning);
this.info.testSuccessCount = this.getCountByType(IInfoItemData_1.InfoItemType.testCompleteSuccess);
this.info.testFailCount = this.getCountByType(IInfoItemData_1.InfoItemType.testCompleteFail);
this.info.testNotApplicableCount = this.getCountByType(IInfoItemData_1.InfoItemType.testCompleteNoApplicableItemsFound);
this.info.errorSummary = this.getSummaryByType(IInfoItemData_1.InfoItemType.error);
this.info.internalProcessingErrorSummary = this.getSummaryByType(IInfoItemData_1.InfoItemType.internalProcessingError);
this.info.warningSummary = this.getSummaryByType(IInfoItemData_1.InfoItemType.warning);
this.info.testFailSummary = this.getSummaryByType(IInfoItemData_1.InfoItemType.testCompleteFail);
const generationEndTime = Date.now();
this.info.infoGenerationTime = generationEndTime - generationStartTime;
if (this.project) {
this.info.endToEndGenerationTime = generationEndTime - this.project.creationTime;
}
if (this.suite === IProjectInfoData_1.ProjectInfoSuite.defaultInDevelopment ||
this.suite === IProjectInfoData_1.ProjectInfoSuite.sharing ||
this.suite === IProjectInfoData_1.ProjectInfoSuite.sharingStrict) {
this.info.reds = this.getRed();
}
this._isGenerating = false;
if (valOperId !== undefined) {
// End the operation - progress bar disappears immediately
await this.project?.creatorTools.notifyOperationEnded(valOperId, "", Status_1.StatusTopic.validation);
}
if (this.project) {
const errorTypes = {};
for (const item of genItems) {
if (item.itemType === IInfoItemData_1.InfoItemType.internalProcessingError) {
const errorType = item.generatorId || "unknown";
errorTypes[errorType] = (errorTypes[errorType] || 0) + 1;
}
}
const properties = {
[TelemetryConstants_1.TelemetryProperties.PROJECT_ITEM_COUNT]: this.project.items.length,
[TelemetryConstants_1.TelemetryProperties.INTERNAL_PROCESSING_ERROR_COUNT]: this.info.internalProcessingErrorCount || 0,
[TelemetryConstants_1.TelemetryProperties.SUITE_TYPE]: this.suite,
};
const errorTypeKeys = Object.keys(errorTypes);
if (errorTypeKeys.length > 0) {
properties[TelemetryConstants_1.TelemetryProperties.ERROR_TYPES] = errorTypeKeys.join(",");
}
Telemetry_1.default.trackEvent({
name: TelemetryConstants_1.TelemetryEvents.VALIDATION_COMPLETED,
properties,
});
}
for (const prom of pendingLoad) {
prom(undefined);
}
}
}
disconnectFromProject() {
this.project = undefined;
for (const pi of this.items) {
pi.disconnect();
}
}
addTestSummations(genItems, genItemsByStoragePath, generators, excludeTests) {
for (const gen of generators) {
if ((!excludeTests || !excludeTests.includes(gen.id)) && this.matchesSuite(gen)) {
const results = ProjectInfoSet.getItemsInCollection(genItems, gen.id);
if (results.length === 0 && !gen.canAlwaysProcess) {
this.pushItem(genItems, genItemsByStoragePath, new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.testCompleteNoApplicableItemsFound, gen.id, 2, `No applicable items found for test ${gen.title} (${gen.id})`));
}
else {
let errorCount = 0;
let internalErrorCount = 0;
let foundTestVerdict = false;
for (const result of results) {
if (result.itemType === IInfoItemData_1.InfoItemType.testCompleteFail ||
result.itemType === IInfoItemData_1.InfoItemType.testCompleteSuccess ||
result.itemType === IInfoItemData_1.InfoItemType.testCompleteNoApplicableItemsFound) {
foundTestVerdict = true;
}
else if (result.itemType === IInfoItemData_1.InfoItemType.error) {
errorCount++;
}
else if (result.itemType === IInfoItemData_1.InfoItemType.internalProcessingError) {
internalErrorCount++;
}
}
if (!foundTestVerdict) {
if (errorCount > 0 && internalErrorCount <= 0) {
this.pushItem(genItems, genItemsByStoragePath, new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.testCompleteFail, gen.id, 0, `Found ${errorCount} error${errorCount !== 1 ? "s" : ""} in ${gen.title} check`));
}
else if (internalErrorCount > 0 && errorCount <= 0) {
this.pushItem(genItems, genItemsByStoragePath, new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.testCompleteFail, gen.id, 0, `Found ${internalErrorCount} internal error${internalErrorCount !== 1 ? "s" : ""} in ${gen.title} check. This may be a temporary issue with the test run.`));
}
else if (errorCount + internalErrorCount > 0) {
this.pushItem(genItems, genItemsByStoragePath, new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.testCompleteFail, gen.id, 0, `Found ${errorCount} error${errorCount !== 1 ? "s" : ""} and ${internalErrorCount} internal error${internalErrorCount !== 1 ? "s" : ""} in ${gen.title} check`));
}
else {
this.pushItem(genItems, genItemsByStoragePath, new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.testCompleteSuccess, gen.id, 1, `${gen.title} completed successfully`));
}
}
}
}
}
}
pushItem(itemSet, itemsByStoragePath, item) {
if (item.projectItem &&
item.projectItem.projectPath &&
item.itemType !== IInfoItemData_1.InfoItemType.info &&
item.itemType !== IInfoItemData_1.InfoItemType.featureAggregate) {
if (!itemsByStoragePath[item.projectItem.projectPath]) {
itemsByStoragePath[item.projectItem.projectPath] = [];
}
itemsByStoragePath[item.projectItem.projectPath]?.push(item);
}
itemSet.push(item);
}
mergeFeatureSetsAndFieldsTo(allFeatureSets, allFields) {
if (!this.info || !this.info.featureSets) {
return;
}
for (const str in this.info) {
if (str !== "features") {
if (Utilities_1.default.isUsableAsObjectKey(str)) {
allFields[str] = true;
}
}
}
for (const str in allFields) {
let inf = this.info;
if (ProjectInfoSet.isAggregableFieldName(str) && Utilities_1.default.isUsableAsObjectKey(str)) {
if (inf[str] === undefined) {
inf[str] = "";
}
}
}
for (const featureName in this.info.featureSets) {
if (Utilities_1.default.isUsableAsObjectKey(featureName)) {
const myFeature = this.info.featureSets[featureName];
if (myFeature !== undefined) {
let allFeature = allFeatureSets[featureName];
if (allFeature === undefined) {
allFeature = {};
allFeatureSets[featureName] = allFeature;
}
for (const measureName in myFeature) {
if (Utilities_1.default.isUsableAsObjectKey(measureName)) {
const measureVal = myFeature[measureName];
if (measureVal !== undefined) {
let allMeasureVal = allFeature[measureName];
if (allMeasureVal === undefined) {
allMeasureVal = measureVal;
}
else {
allMeasureVal += measureVal;
}
allFeature[measureName] = allMeasureVal;
}
}
}
}
}
}
}
ensureGenerators() {
if (!this.info) {
return;
}
if (this.info.summary !== undefined) {
return;
}
this.info.summary = {};
for (let i = 0; i < this.items.length; i++) {
const gId = this.items[i].generatorId;
const gIndex = this.items[i].generatorIndex;
let gen = this.info.summary[gId];
if (gen === undefined) {
gen = {};
this.info.summary[gId] = gen;
}
let genI = gen[gIndex];
if (genI === undefined) {
genI = {};
gen[gIndex] = genI;
}
const topicInfo = ProjectInfoSet.getTopicData(gId, gIndex);
if (topicInfo) {
genI.title = topicInfo.title;
}
if (genI.defaultMessage === undefined && this.items[i].message !== genI.title) {
genI.defaultMessage = this.items[i].message;
}
}
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i];
const gId = item.generatorId;
const gIndex = item.generatorIndex;
let gen = this.info.summary[gId];
if (gen === undefined) {
gen = {};
this.info.summary[gId] = gen;
}
let genI = gen[gIndex];
if (genI === undefined) {
genI = {};
gen[gIndex] = genI;
}
if (item.itemType !== IInfoItemData_1.InfoItemType.featureAggregate) {
switch (item.itemType) {
case IInfoItemData_1.InfoItemType.error:
genI.errors = genI.errors ? genI.errors + 1 : 1;
break;
case IInfoItemData_1.InfoItemType.testCompleteFail:
genI.testCompleteFails = genI.testCompleteFails ? genI.testCompleteFails + 1 : 1;
break;
case IInfoItemData_1.InfoItemType.testCompleteSuccess:
genI.testCompleteSuccesses = genI.testCompleteSuccesses ? genI.testCompleteSuccesses + 1 : 1;
break;
case IInfoItemData_1.InfoItemType.warning:
genI.warnings = genI.warnings ? genI.warnings + 1 : 1;
break;
case IInfoItemData_1.InfoItemType.recommendation:
genI.recommendations = genI.recommendations ? genI.recommendations + 1 : 1;
break;
case IInfoItemData_1.InfoItemType.internalProcessingError:
genI.internalProcessingErrors = genI.internalProcessingErrors ? genI.internalProcessingErrors + 1 : 1;
break;
}
}
if (this.items[i].message === genI.defaultMessage || this.items[i].message === genI.title) {
this.items[i].message = undefined;
}
}
}
itemToString(item) {
let summaryString = item.typeSummaryShort + ": ";
summaryString += "[" + item.generatorId + Utilities_1.default.frontPadToLength(item.generatorIndex, 3, "0") + "]";
if (item.shortProjectItemPath) {
summaryString += " (" + item.shortProjectItemPath + ")";
}
let effectiveMessage = this.getEffectiveMessage(item);
if (effectiveMessage.length > 0) {
summaryString += " " + effectiveMessage;
}
if (item.data) {
summaryString += ": " + item.data;
}
const errorContent = item.contentSummary;
if (errorContent) {
summaryString += " [in " + errorContent + "]";
}
return summaryString;
}
static getExtendedMessageFromData(data, item) {
return (this.getEffectiveMessageFromData(data, item) + (item.d ? ": " + item.d : "") + (item.p ? " - " + item.p : ""));
}
static getEffectiveMessageFromData(data, item) {
if (item.m !== undefined) {
return item.m;
}
if (data.info === undefined || data.info.summary === undefined) {
return undefined;
}
let gen = data.info.summary[item.gId];
if (gen === undefined) {
return undefined;
}
let genI = gen[item.gIx];
if (genI === undefined) {
return undefined;
}
return genI.defaultMessage;
}
getEffectiveMessage(item) {
if (item.message !== undefined) {
return item.message;
}
if (this.info === undefined || this.info.summary === undefined) {
return "";
}
let gen = this.info.summary[item.generatorId];
if (gen === undefined) {
return "";
}
let genI = gen[item.generatorIndex];
if (genI === undefined) {
return "";
}
return genI.defaultMessage ? genI.defaultMessage : "";
}
shouldIncludeInIndex(data) {
if (data.gId === "JSON" || data.gId === "ESLINT") {
return false;
}
return true;
}
getDataObject(sourceName, sourcePath, sourceHash, isIndexOnly, subsetReports) {
const items = [];
this.ensureGenerators();
for (let i = 0; i < this.items.length; i++) {
const dataObj = this.items[i].dataObject;
if (!isIndexOnly || this.shouldIncludeInIndex(dataObj)) {
items.push(dataObj);
}
}
Utilities_1.default.encodeObjectWithSequentialRunLengthEncodeUsingNegative(this.contentIndex.data.trie);
return {
info: this.info,
items: items,
index: this.contentIndex.data,
generatorName: Constants_1.constants.name,
suite: this.suite,
subsetReports: subsetReports,
generatorVersion: Constants_1.constants.version,
sourceName: sourceName,
sourcePath: sourcePath,
sourceHash: sourceHash,
};
}
static isAggregableFieldName(name) {
if (name !== "features" && name !== "summary" && name !== "featureSets" && name !== "defaultIcon") {
return true;
}
return false;
}
static isAggregableFeatureMeasureName(name) {
if (!name.startsWith("#")) {
return true;
}
return false;
}
static getSummaryCsvHeaderLine(projectInfo, allFeatures) {
let csvLine = "Name,Title,Area,";
let fieldNames = [];
for (const str in projectInfo) {
if (ProjectInfoSet.isAggregableFieldName(str)) {
fieldNames.push(str);
}
}
fieldNames = fieldNames.sort(ProjectInfoSet.sortMinecraftFeatures);
for (const str of fieldNames) {
csvLine += Utilities_1.default.humanifyJsName(str) + ",";
}
for (const str in projectInfo) {
if (ProjectInfoSet.isAggregableFieldName(str)) {
fieldNames.push(str);
}
}
for (const featureName in allFeatures) {
const feature = allFeatures[featureName];
if (feature) {
for (const measureName in feature) {
if (ProjectInfoSet.isAggregableFeatureMeasureName(measureName)) {
const measure = feature[measureName];
if (meas