MISC: Add removal of fuzzy matching to list of breaking changes (#2149)

This commit is contained in:
catloversg
2025-05-30 17:38:56 +07:00
committed by GitHub
parent 296295b490
commit e34a29c458
4 changed files with 194 additions and 148 deletions

View File

@@ -1,31 +1,33 @@
import { APIBreakInfo } from "./APIBreak";
import type { VersionBreakingChange } from "./APIBreak";
export const breakInfos261: APIBreakInfo[] = [
{
brokenAPIs: [{ name: "ns.bladeburner.getCurrentAction" }],
info:
"ns.bladeburner.getCurrentAction:\n" +
'When not performing a bladeburner action, previously returned {type: "Idle", name: ""}, now returns null.\n' +
"Because of this change, the null case now needs to be dealt with prior to accessing properties on the return of getCurrentAction, including destructuring.\n" +
"Additionally, any existing code for filtering out the Idle case will need to be adjusted.\n\n" +
"See https://github.com/bitburner-official/bitburner-src/issues/1249 or PR https://github.com/bitburner-official/bitburner-src/pull/1248 for more details.",
showPopUp: true,
},
{
brokenAPIs: [
{ name: "ns.bladeburner.getActionCountRemaining" },
{ name: "ns.bladeburner.getActionEstimatedSuccessChance" },
{ name: "ns.bladeburner.getActionTime" },
],
info:
"ns.bladeburner.getActionCountRemaining:\n" +
'Previously returned -1 when called with type "Idle" and name "". This is no longer valid usage and will result in an error.\n\n' +
"ns.bladeburner.getActionEstimatedSuccessChance:\n" +
'Previously returned [-1, -1] when called with type "Idle" and name "". This is no longer valid usage and will result in an error.\n\n' +
"ns.bladeburner.getActionTime:\n" +
'Previously returned -1 when called with type "Idle" and name "". This is no longer valid usage and will result in an error.\n\n' +
"See the related changes for ns.bladeburner.getCurrentAction, which were shown earlier in these API break details.\n" +
"In most cases, the fixes for ns.bladeburner.getCurrentAction will fix this group of issues as well.",
showPopUp: true,
},
];
export const breakInfos261: VersionBreakingChange = {
apiBreakingChanges: [
{
brokenAPIs: [{ name: "ns.bladeburner.getCurrentAction" }],
info:
"ns.bladeburner.getCurrentAction:\n" +
'When not performing a bladeburner action, previously returned {type: "Idle", name: ""}, now returns null.\n' +
"Because of this change, the null case now needs to be dealt with prior to accessing properties on the return of getCurrentAction, including destructuring.\n" +
"Additionally, any existing code for filtering out the Idle case will need to be adjusted.\n\n" +
"See https://github.com/bitburner-official/bitburner-src/issues/1249 or PR https://github.com/bitburner-official/bitburner-src/pull/1248 for more details.",
showPopUp: true,
},
{
brokenAPIs: [
{ name: "ns.bladeburner.getActionCountRemaining" },
{ name: "ns.bladeburner.getActionEstimatedSuccessChance" },
{ name: "ns.bladeburner.getActionTime" },
],
info:
"ns.bladeburner.getActionCountRemaining:\n" +
'Previously returned -1 when called with type "Idle" and name "". This is no longer valid usage and will result in an error.\n\n' +
"ns.bladeburner.getActionEstimatedSuccessChance:\n" +
'Previously returned [-1, -1] when called with type "Idle" and name "". This is no longer valid usage and will result in an error.\n\n' +
"ns.bladeburner.getActionTime:\n" +
'Previously returned -1 when called with type "Idle" and name "". This is no longer valid usage and will result in an error.\n\n' +
"See the related changes for ns.bladeburner.getCurrentAction, which were shown earlier in these API break details.\n" +
"In most cases, the fixes for ns.bladeburner.getCurrentAction will fix this group of issues as well.",
showPopUp: true,
},
],
};

View File

@@ -1,112 +1,133 @@
import { APIBreakInfo } from "./APIBreak";
import type { VersionBreakingChange } from "./APIBreak";
export const breakInfos300: APIBreakInfo[] = [
{
brokenAPIs: [{ name: "ns.nFormat" }],
info:
"ns.nFormat() was removed.\n" +
"Use ns.formatNumber, ns.formatRam, ns.formatPercent, or JS built-in objects/functions (e.g., Intl.NumberFormat, " +
"Intl.PluralRules, Intl.Locale) instead.",
showPopUp: true,
},
{
brokenAPIs: [
{
name: "ns.getTimeSinceLastAug",
migration: {
searchValue: "ns.getTimeSinceLastAug()",
replaceValue: "(Date.now() - ns.getResetInfo().lastAugReset)",
export const breakingChanges300: VersionBreakingChange = {
additionalText: "For more information, please check https://github.com/bitburner-official/bitburner-src/issues/2148.",
apiBreakingChanges: [
{
brokenAPIs: [{ name: "ns.nFormat" }],
info:
"ns.nFormat() was removed.\n" +
"Use ns.formatNumber, ns.formatRam, ns.formatPercent, or JS built-in objects/functions (e.g., Intl.NumberFormat, " +
"Intl.PluralRules, Intl.Locale) instead.",
showPopUp: true,
},
{
brokenAPIs: [
{
name: "ns.getTimeSinceLastAug",
migration: {
searchValue: "ns.getTimeSinceLastAug()",
replaceValue: "(Date.now() - ns.getResetInfo().lastAugReset)",
},
},
},
{
name: "ns.getPlayer().playtimeSinceLastAug",
migration: {
searchValue: "ns.getPlayer().playtimeSinceLastAug",
replaceValue: "(Date.now() - ns.getResetInfo().lastAugReset)",
{
name: "ns.getPlayer().playtimeSinceLastAug",
migration: {
searchValue: "ns.getPlayer().playtimeSinceLastAug",
replaceValue: "(Date.now() - ns.getResetInfo().lastAugReset)",
},
},
},
{
name: "ns.getPlayer().playtimeSinceLastBitnode",
migration: {
searchValue: "ns.getPlayer().playtimeSinceLastBitnode",
replaceValue: "(Date.now() - ns.getResetInfo().lastNodeReset)",
{
name: "ns.getPlayer().playtimeSinceLastBitnode",
migration: {
searchValue: "ns.getPlayer().playtimeSinceLastBitnode",
replaceValue: "(Date.now() - ns.getResetInfo().lastNodeReset)",
},
},
},
{
name: "ns.getPlayer().bitNodeN",
migration: {
searchValue: "ns.getPlayer().bitNodeN",
replaceValue: "ns.getResetInfo().currentNode",
{
name: "ns.getPlayer().bitNodeN",
migration: {
searchValue: "ns.getPlayer().bitNodeN",
replaceValue: "ns.getResetInfo().currentNode",
},
},
},
{
name: "ns.corporation.getCorporation().state",
migration: {
searchValue: "ns.corporation.getCorporation().state",
replaceValue: "ns.corporation.getCorporation().nextState",
{
name: "ns.corporation.getCorporation().state",
migration: {
searchValue: "ns.corporation.getCorporation().state",
replaceValue: "ns.corporation.getCorporation().nextState",
},
},
},
],
info:
"ns.getTimeSinceLastAug was removed.\n" +
'It has been automatically replaced with "Date.now() - ns.getResetInfo().lastAugReset".\n\n' +
"ns.getPlayer().playtimeSinceLastAug was removed.\n" +
'It has been automatically replaced with "Date.now() - ns.getResetInfo().lastAugReset".\n\n' +
"ns.getPlayer().playtimeSinceLastBitnode was removed.\n" +
'It has been automatically replaced with "Date.now() - ns.getResetInfo().lastNodeReset".\n\n' +
"ns.getPlayer().bitNodeN was removed.\n" +
'It has been automatically replaced with "ns.getResetInfo().currentNode"\n\n' +
"ns.corporation.getCorporation().state was removed.\n" +
'It has been automatically replaced with "ns.corporation.getCorporation().nextState"',
showPopUp: false,
},
{
brokenAPIs: [
{ name: "ns.formatNumber", migration: { searchValue: "ns.formatNumber", replaceValue: "ns.format.number" } },
{ name: "ns.formatRam", migration: { searchValue: "ns.formatRam", replaceValue: "ns.format.ram" } },
{ name: "ns.formatPercent", migration: { searchValue: "ns.formatPercent", replaceValue: "ns.format.percent" } },
{ name: "ns.tFormat", migration: { searchValue: "ns.tFormat", replaceValue: "ns.format.time" } },
],
info:
"The formatting functions have been moved to their own interface, ns.format.\n" +
"Each function has been replaced with their corresponding interface variant.\n" +
"Additionally, the naming of ns.tFormat has been changed to ns.format.time.",
showPopUp: false,
},
{
brokenAPIs: [
{ name: "ns.tail", migration: { searchValue: "ns.tail", replaceValue: "ns.ui.openTail" } },
{ name: "ns.moveTail", migration: { searchValue: "ns.moveTail", replaceValue: "ns.ui.moveTail" } },
{ name: "ns.resizeTail", migration: { searchValue: "ns.resizeTail", replaceValue: "ns.ui.resizeTail" } },
{ name: "ns.closeTail", migration: { searchValue: "ns.closeTail", replaceValue: "ns.ui.closeTail" } },
{ name: "ns.setTitle", migration: { searchValue: "ns.setTitle", replaceValue: "ns.ui.setTailTitle" } },
],
info:
"ns.tail() was removed.\n" +
'It has been automatically replaced with "ns.ui.openTail()".\n\n' +
"ns.moveTail() was removed.\n" +
'It has been automatically replaced with "ns.ui.moveTail()".\n\n' +
"ns.resizeTail() was removed.\n" +
'It has been automatically replaced with "ns.ui.resizeTail()".\n\n' +
"ns.closeTail() was removed.\n" +
'It has been automatically replaced with "ns.ui.closeTail()".\n\n' +
"ns.setTitle() was removed.\n" +
'It has been automatically replaced with "ns.ui.setTailTitle()".\n\n',
showPopUp: false,
},
{
brokenAPIs: [
{
name: "ns.corporation.setAutoJobAssignment",
migration: {
searchValue: "setAutoJobAssignment",
replaceValue: "setJobAssignment",
],
info:
"ns.getTimeSinceLastAug was removed.\n" +
'It has been automatically replaced with "Date.now() - ns.getResetInfo().lastAugReset".\n\n' +
"ns.getPlayer().playtimeSinceLastAug was removed.\n" +
'It has been automatically replaced with "Date.now() - ns.getResetInfo().lastAugReset".\n\n' +
"ns.getPlayer().playtimeSinceLastBitnode was removed.\n" +
'It has been automatically replaced with "Date.now() - ns.getResetInfo().lastNodeReset".\n\n' +
"ns.getPlayer().bitNodeN was removed.\n" +
'It has been automatically replaced with "ns.getResetInfo().currentNode"\n\n' +
"ns.corporation.getCorporation().state was removed.\n" +
'It has been automatically replaced with "ns.corporation.getCorporation().nextState"',
showPopUp: false,
},
{
brokenAPIs: [
{ name: "ns.formatNumber", migration: { searchValue: "ns.formatNumber", replaceValue: "ns.format.number" } },
{ name: "ns.formatRam", migration: { searchValue: "ns.formatRam", replaceValue: "ns.format.ram" } },
{ name: "ns.formatPercent", migration: { searchValue: "ns.formatPercent", replaceValue: "ns.format.percent" } },
{ name: "ns.tFormat", migration: { searchValue: "ns.tFormat", replaceValue: "ns.format.time" } },
],
info:
"The formatting functions have been moved to their own interface, ns.format.\n" +
"Each function has been replaced with their corresponding interface variant.\n" +
"Additionally, the naming of ns.tFormat has been changed to ns.format.time.",
showPopUp: false,
},
{
brokenAPIs: [
{ name: "ns.tail", migration: { searchValue: "ns.tail", replaceValue: "ns.ui.openTail" } },
{ name: "ns.moveTail", migration: { searchValue: "ns.moveTail", replaceValue: "ns.ui.moveTail" } },
{ name: "ns.resizeTail", migration: { searchValue: "ns.resizeTail", replaceValue: "ns.ui.resizeTail" } },
{ name: "ns.closeTail", migration: { searchValue: "ns.closeTail", replaceValue: "ns.ui.closeTail" } },
{ name: "ns.setTitle", migration: { searchValue: "ns.setTitle", replaceValue: "ns.ui.setTailTitle" } },
],
info:
"ns.tail() was removed.\n" +
'It has been automatically replaced with "ns.ui.openTail()".\n\n' +
"ns.moveTail() was removed.\n" +
'It has been automatically replaced with "ns.ui.moveTail()".\n\n' +
"ns.resizeTail() was removed.\n" +
'It has been automatically replaced with "ns.ui.resizeTail()".\n\n' +
"ns.closeTail() was removed.\n" +
'It has been automatically replaced with "ns.ui.closeTail()".\n\n' +
"ns.setTitle() was removed.\n" +
'It has been automatically replaced with "ns.ui.setTailTitle()".\n\n',
showPopUp: false,
},
{
brokenAPIs: [
{
name: "ns.corporation.setAutoJobAssignment",
migration: {
searchValue: "setAutoJobAssignment",
replaceValue: "setJobAssignment",
},
},
},
],
info:
"ns.corporation.setAutoJobAssignment() was removed.\n" +
'It has been automatically replaced with "ns.corporation.setJobAssignment()".\n\n',
showPopUp: false,
},
];
],
info:
"ns.corporation.setAutoJobAssignment() was removed.\n" +
'It has been automatically replaced with "ns.corporation.setJobAssignment()".\n\n',
showPopUp: false,
},
{
brokenAPIs: [],
info:
"With some APIs, when you passed values to their params, you could pass a value that was not an exact match. " +
'For example, with "ns.singularity.commitCrime", you could pass "Rob Store", "rob store", "RobStore", "robstore", "robStore", etc. ' +
'This is called "fuzzy matching". Now, you must pass an exact value (i.e., Rob Store). This change affects:\n' +
"- Bladeburner action and type: BladeburnerActionType, BladeburnerGeneralActionName, BladeburnerContractName, BladeburnerOperationName, BladeburnerBlackOpName, SpecialBladeburnerActionTypeForSleeve, BladeburnerActionTypeForSleeve.\n" +
"- Crime: CrimeType\n" +
"- Faction work: FactionWorkType\n" +
"- University class: UniversityClassType\n" +
"- Gym stat: GymType\n" +
"- Job field: JobField\n" +
"- Stock position: PositionType\n" +
"- Stock order: OrderType\n" +
"You can access these values via ns.enums and Bladeburner APIs.",
showPopUp: true,
doNotSkip: true,
},
],
};

View File

@@ -19,6 +19,11 @@ type ScriptImpactMap = Map<ScriptFilePath, number[]>;
/** For an overall API break, map of server hostnames to an array of impacted scripts */
type ImpactMap = Map<string, ScriptImpactMap>;
export interface VersionBreakingChange {
additionalText?: string;
apiBreakingChanges: APIBreakInfo[];
}
export interface APIBreakInfo {
/** The API functions impacted by the API break */
brokenAPIs: {
@@ -33,6 +38,19 @@ export interface APIBreakInfo {
info: string;
/** If broken APIs can be safely migrated, we can skip displaying the notification popup */
showPopUp: boolean;
/**
* With a new version with breaking changes, the "showAPIBreaks" function checks all breaking changes and does 2
* things with changes that affect the player's scripts:
* - Write info of changes to a log file.
* - Show popups per change.
* Note that we skip changes that do not affect the player's scripts. This is problematic with some breaking changes.
*
* With each breaking change in "brokenAPIs", we try to detect the affected code by using "name" or
* "migration.searchValue". However, with some breaking changes, we cannot detect the affected code reliably via
* "brokenAPIs". In this case, instead of skipping them, we always "process" that change (i.e., write info to the log
* file and optionally show a popup that notifies the player about this change).
*/
doNotSkip?: boolean;
}
function detectImpactAndMigrateLines(script: Script, brokenFunctions: APIBreakInfo["brokenAPIs"]): number[] | null {
@@ -54,7 +72,7 @@ function detectImpactAndMigrateLines(script: Script, brokenFunctions: APIBreakIn
}
/** Returns a map keyed by hostname */
function detectImpactAndMigrate(brokenFunctions: APIBreakInfo["brokenAPIs"]): ImpactMap | null {
function detectImpactAndMigrate(brokenFunctions: APIBreakInfo["brokenAPIs"]): ImpactMap {
const returnMap = new Map<string, ScriptImpactMap>();
for (const server of GetAllServers()) {
const impactedScripts = new Map<ScriptFilePath, number[]>();
@@ -68,24 +86,25 @@ function detectImpactAndMigrate(brokenFunctions: APIBreakInfo["brokenAPIs"]): Im
returnMap.set(server.hostname, impactedScripts);
}
}
return returnMap.size ? returnMap : null;
return returnMap;
}
/** Show the player a dialog for their API breaks, and save an info file for the player to review later */
export function showAPIBreaks(version: string, ...breakInfos: APIBreakInfo[]) {
export function showAPIBreaks(version: string, { additionalText, apiBreakingChanges }: VersionBreakingChange) {
const details: {
text: string;
showPopUp: boolean;
}[] = [];
let numberOfPopUps = 0;
for (const breakInfo of breakInfos) {
for (const breakInfo of apiBreakingChanges) {
const impactMap = detectImpactAndMigrate(breakInfo.brokenAPIs);
if (!impactMap) {
// Skip processing if we don't find any affected code and the breaking change does not enable the "doNotSkip" flag.
if (impactMap.size === 0 && !breakInfo.doNotSkip) {
continue;
}
details.push({
text:
breakInfo.info +
let detailText = breakInfo.info;
if (impactMap.size > 0) {
detailText +=
`\n\nUsage of the following functions may have been affected:\n${breakInfo.brokenAPIs
.map((func) => func.name)
.join("\n")}\n\n` +
@@ -102,7 +121,10 @@ export function showAPIBreaks(version: string, ...breakInfos: APIBreakInfo[]) {
)
.join("\n"),
)
.join("\n\n"),
.join("\n\n");
}
details.push({
text: detailText,
showPopUp: breakInfo.showPopUp,
});
if (breakInfo.showPopUp) {
@@ -125,7 +147,8 @@ export function showAPIBreaks(version: string, ...breakInfos: APIBreakInfo[]) {
dialogBoxCreate(
`SOME OF YOUR SCRIPTS HAVE POTENTIALLY BEEN IMPACTED BY AN API BREAK, DUE TO CHANGES IN VERSION ${version}\n\n` +
"The following dialog boxes will provide details of the potential impact to your scripts.\n" +
`A file with these details has also been saved on your home computer under filename ${textFileName}.`,
`A file with these details has also been saved on your home computer under filename ${textFileName}.` +
(additionalText ? `\n\n${additionalText}` : ""),
);
let popUpIndex = 0;
for (const detail of details) {

View File

@@ -30,7 +30,7 @@ import { exportMaterial } from "../Corporation/Actions";
import { getGoSave, loadGo } from "../Go/SaveLoad";
import { showAPIBreaks } from "./APIBreaks/APIBreak";
import { breakInfos261 } from "./APIBreaks/2.6.1";
import { breakInfos300 } from "./APIBreaks/3.0.0";
import { breakingChanges300 } from "./APIBreaks/3.0.0";
/** Function for performing a series of defined replacements. See 0.58.0 for usage */
function convert(code: string, changes: [RegExp, string][]): string {
@@ -527,7 +527,7 @@ Error: ${e}`,
loadGo(JSON.stringify(freshSaveData));
}
if (ver < 39) {
showAPIBreaks("2.6.1", ...breakInfos261);
showAPIBreaks("2.6.1", breakInfos261);
}
if (ver < 42) {
// All whitespace except for spaces was allowed in filenames
@@ -547,6 +547,6 @@ Error: ${e}`,
if (found) Terminal.error("Filenames with whitespace found and corrected, see console for details.");
}
if (ver < 44) {
showAPIBreaks("3.0.0", ...breakInfos300);
showAPIBreaks("3.0.0", breakingChanges300);
}
}