@bscotch/gml-parser
Version:
A parser for GML (GameMaker Language) files for programmatic manipulation and analysis of GameMaker projects.
387 lines • 18.5 kB
TypeScript
import { Pathy } from '@bscotch/pathy';
import { type StitchConfig } from '@bscotch/stitch-config';
import { type Yyp, type YypFolder, type YypResource } from '@bscotch/yy';
import { EventEmitter } from 'events';
import { ImportModuleOptions } from './modules.types.js';
import { Asset } from './project.asset.js';
import { Code } from './project.code.js';
import { Diagnostic } from './project.diagnostics.js';
import { Native } from './project.native.js';
import { Signifier } from './signifiers.js';
import { StructType, Type } from './types.js';
export { setLogger, type Logger } from './logger.js';
export interface SymbolInfo {
native: boolean;
symbol: Signifier | Type;
}
export interface DiagnosticsEventPayload {
filePath: string;
code?: Code;
diagnostics: Diagnostic[];
}
export type OnDiagnostics = (diagnostics: DiagnosticsEventPayload) => void;
export interface ProjectOptions {
/**
* Register a callback to run when diagnostics are emitted.
* If not provided, a callback can be registered after
* initialization, but will not receive any diagnostics
* from the initial parse.
*/
onDiagnostics?: OnDiagnostics;
/**
* If registered, this callback will be used to report
* when progress has been made towards loading the project.
*/
onLoadProgress?: (increment: number, message?: string) => void;
settings?: {
/**
* Symbols starting with these prefixes that are not declared
* in the coded will be treated as global symbols. This is
* useful for ensuring globals created by extensions will not
* show up as being undeclared.
*/
autoDeclareGlobalsPrefixes?: string[];
};
}
export declare class Project {
readonly yypPath: Pathy<any>;
readonly options?: ProjectOptions | undefined;
yyp: Yyp;
/** Until this resolves, assume that this.yyp is not yet read */
yypWaiter?: Promise<any>;
config: StitchConfig;
readonly assets: Map<string, Asset<"animcurves" | "extensions" | "fonts" | "notes" | "objects" | "particles" | "paths" | "rooms" | "roomui" | "scripts" | "sequences" | "shaders" | "sounds" | "sprites" | "tilesets" | "timelines">>;
/**
* Store the "native" functions, constants, and enums on
* a per-project basis, but separately from the project-specific
* symbols. The native symbols and types are loaded from the spec,
* so they can vary between projects. */
native: Native;
helpLinks: {
[method: string]: string;
};
/**
* When resolved, the GML spec has been loaded and the
* `native` property has been populated.
*/
nativeWaiter?: Promise<void>;
/**
* The type of the 'global' struct, which contains all globalvars
* and globally defined functions. */
self: StructType;
/**
* The `global` symbol, which has type `self`. */
symbol: Signifier;
/**
* Non-native global types, which can be referenced in JSDocs
* and in a symbol's types. */
readonly types: Map<string, Type<"Real" | "Undefined" | "Enum" | "Function" | "Array" | "Bool" | "Pointer" | "String" | "Struct" | "ArgumentIdentity" | "Any" | "Asset.GMAnimCurve" | "Asset.GMAudioGroup" | "Asset.GMFont" | "Asset.GMObject" | "Asset.GMParticleSystem" | "Asset.GMPath" | "Asset.GMRoom" | "Asset.GMScript" | "Asset.GMSequence" | "Asset.GMShader" | "Asset.GMSound" | "Asset.GMSprite" | "Asset.GMTileSet" | "Asset.GMTimeline" | "Asset.Script" | "Id.AudioEmitter" | "Id.AudioListener" | "Id.AudioSyncGroup" | "Id.BackgroundElement" | "Id.BinaryFile" | "Id.Buffer" | "Id.Camera" | "Id.DsGrid" | "Id.DsList" | "Id.DsMap" | "Id.DsPriority" | "Id.DsQueue" | "Id.DsStack" | "Id.ExternalCall" | "Id.Gif" | "Id.Instance" | "Id.Layer" | "Id.MpGrid" | "Id.ParticleEmitter" | "Id.ParticleSystem" | "Id.ParticleType" | "Id.PhysicsIndex" | "Id.PhysicsParticleGroup" | "Id.Sampler" | "Id.SequenceElement" | "Id.Socket" | "Id.Sound" | "Id.SpriteElement" | "Id.Surface" | "Id.TextFile" | "Id.Texture" | "Id.TileElementId" | "Id.TileMapElement" | "Id.TimeSource" | "Id.Uniform" | "Id.VertexBuffer" | "Id.VertexFormat" | "Mixed" | "EnumMember" | "Unknown" | "Never" | "InstanceType" | "ObjectType" | "StaticType">>;
protected emitter: EventEmitter<[never]>;
/** Code that needs to be reprocessed, for one reason or another. */
protected dirtyFiles: Set<Code>;
protected constructor(yypPath: Pathy<any>, options?: ProjectOptions | undefined);
/**
* @internal For tracking changed code files that will need to be re-parsed.
*/
queueDirtyFileUpdate(code: Code): void;
/**
* @internal Drain the queue of dirty files, updating their diagnostics
*/
drainDirtyFileUpdateQueue(): void;
/**
* Update the YYP file to list a specific GameMaker IDE version.
* Note that the GameMaker IDE will overwrite this with whatever
* its own version is -- this feature is useful for external tools
* like Stitch that can manage multiple GameMaker IDE versions.
*/
setIdeVersion(version: string): Promise<void>;
/**
* The current version of the GameMaker IDE listed in
* this project's YYP file. This is the GameMaker version that
* the project was last opened with.
*/
get ideVersion(): string;
/**
* The directory in which the project lives.
*/
get dir(): Pathy;
/**
* Get the Stitch config for this project, which defines
* various settings that may impact rule around adding
* assets, parsing logs, etc.
*/
get stitchConfig(): Pathy<{
[x: string]: unknown;
$schema: "https://raw.githubusercontent.com/bscotch/stitch/develop/packages/config/schemas/stitch.config.schema.json";
textureGroupAssignments?: Record<string, string> | undefined;
audioGroupAssignments?: Record<string, string> | undefined;
runtimeVersion?: string | undefined;
newSpriteRules?: {
[x: string]: unknown;
allowedNames?: string[] | undefined;
} | undefined;
newSoundRules?: {
[x: string]: unknown;
allowedNames?: string[] | undefined;
defaults?: Record<string, {
mono?: boolean | undefined;
}> | undefined;
} | undefined;
gameConsoleStyle?: {
[x: string]: unknown;
base?: string | undefined;
lines?: {
[x: string]: unknown;
pattern: string;
base?: string | undefined;
description?: string | undefined;
caseSensitive?: boolean | undefined;
styles?: {
[x: string]: string;
} | undefined;
}[] | undefined;
} | undefined;
}>;
/** List the names of the GameMaker configs defined by this project. */
get configs(): string[];
/** List the project's "datafiles" (a.k.a. "Included Files"), in the same format as they appear in the YYP file. */
get datafiles(): {
name: string;
CopyToMask: bigint;
filePath: string;
resourceType: "GMIncludedFile";
resourceVersion: string;
ConfigValues?: Record<string, {
CopyToMask: string;
}> | undefined;
}[];
/** List the Folders in this project, normalized to regular POSIX paths
* @example ['my/folder', 'my/other/folder']
*/
get folders(): string[];
/**
* Run a callback when diagnostics are emitted. Returns an unsubscribe function. */
onDiagnostics(callback: OnDiagnostics): () => void;
/** @internal Method that can be called after some code has been parsed to report diagnostics to listeners. */
emitDiagnostics(code: Code | string, diagnostics: Diagnostic[]): void;
/**
* Since GameMaker assets are global they must have unique names independent of their type. Find an asset give it's name. Note that this is case-insensitive!
* @param name The name of the asset to find, case-insensitive.
*/
getAssetByName<Assert extends boolean>(name: string | undefined, options?: {
assertExists: Assert;
}): Assert extends true ? Asset : Asset | undefined;
/**
* @param name The name of the asset to find and remove, case-insensitive.
*/
removeAssetByName(name: string | undefined): Promise<void>;
getAsset(path: Pathy<any>): Asset | undefined;
getGmlFile(path: Pathy<any>): Code | undefined;
/** Normalize path information for a datafile ("Included File") */
parseIncludedFilePath(filePath: string, name?: string): {
filePath: string;
name: string;
};
findIncludedFile(filePath: string, name?: string): {
name: string;
CopyToMask: bigint;
filePath: string;
resourceType: "GMIncludedFile";
resourceVersion: string;
ConfigValues?: Record<string, {
CopyToMask: string;
}> | undefined;
} | undefined;
/**
* Ensure that the included files listed in the YYP exactly match
* the files in the `datafiles` directory.
*/
syncIncludedFiles(): Promise<void>;
/** @internal Load an Asset instance into the project's data model. For use by methods that load the project, add assets, etc. */
registerAsset(resource: Asset): void;
/**
* @param from The name of the asset to rename, case-insensitive.
* @param to The new name for the asset, which must be a valid identifier that doesn't already have an associated asset. The name will be set in the provided casing, but must be unique case-insensitively.
*/
renameAsset(from: string, to: string): Promise<void>;
renameSignifier(signifier: Signifier, newName: string): Promise<void>;
import(fromProject: Project | string, options?: ImportModuleOptions): Promise<{
created: string[];
updated: string[];
errors: string[];
skipped: string[];
}>;
duplicateAsset(sourceName: string, newPath: string): Promise<Asset<"animcurves" | "extensions" | "fonts" | "notes" | "objects" | "particles" | "paths" | "rooms" | "roomui" | "scripts" | "sequences" | "shaders" | "sounds" | "sprites" | "tilesets" | "timelines">>;
/**
* Create a new sound asset. Will not do anything if the asset by this name already exists (but will log an error).
* @param newSoundPath The POSIX-style path within the asset tree where you want this sound to be created, where the last component is the name of the sound asset.
* @param fromFile The path to the source sound file to copy into the new asset's directory.
* @example project.createSound('folder/of/sounds/snd_my_new_sound', 'path/to/sound.mp3');
*/
createSound(newSoundPath: string, fromFile: string | Pathy): Promise<Asset<"animcurves" | "extensions" | "fonts" | "notes" | "objects" | "particles" | "paths" | "rooms" | "roomui" | "scripts" | "sequences" | "shaders" | "sounds" | "sprites" | "tilesets" | "timelines"> | undefined>;
/**
* Create a new room asset. Will not do anything if the asset by this name already exists (but will log an error).
* @param newRoomPath The POSIX-style path within the asset tree where you want this asset to be created, where the last component is the name of the asset.
* @example project.createRoom('folder/of/rooms/rm_my_room');
*/
createRoom(newRoomPath: string): Promise<Asset<'rooms'> | undefined>;
/**
* Create a new sprite asset. Will not do anything if the asset by this name already exists (but will log an error).
* @param newSpritePath The POSIX-style path within the asset tree where you want this asset to be created, where the last component is the name of the asset.
* @param fromImageFile Path to a source PNG image to use as the first frame of the sprite.
* @example project.createSprite('folder/of/sprites/sp_my_sprite', 'path/to/sprite.png');
*/
createSprite(newSpritePath: string, fromImageFile: string | Pathy): Promise<Asset<'sprites'> | undefined>;
/**
* Add an object to the yyp file. The string can include separators,
* in which case folders will be ensured up to the final component.
* @param newObjectName The POSIX-style path within the asset tree where you want this asset to be created, where the last component is the name of the asset.
*/
createObject(newObjectName: string): Promise<Asset<'objects'> | undefined>;
createShader(path: string): Promise<Asset<'shaders'> | undefined>;
/**
* Add a script to the yyp file. The path string can include separators,
* in which case folders will be ensured up to the final component.
*/
createScript(path: string): Promise<Asset<'scripts'> | undefined>;
protected parseNewAssetPath(path: string): Promise<{
folder: {
name: string;
folderPath: string;
resourceType: "GMFolder";
resourceVersion: string;
tags?: string[] | undefined;
order?: number | undefined;
};
name: string;
} | undefined>;
/**
* Given the path to a yy file for an asset, ensure
* it has an entry in the yyp file. */
addAssetToYyp(yyPath: string, options?: {
skipSave?: boolean;
}): Promise<YypResource>;
protected parseFolderPath(path: string | string[]): {
parts: string[];
full: string;
prefix: string;
};
listAssetsInFolder(path: string | string[], options?: {
recursive: boolean;
}): Asset<"animcurves" | "extensions" | "fonts" | "notes" | "objects" | "particles" | "paths" | "rooms" | "roomui" | "scripts" | "sequences" | "shaders" | "sounds" | "sprites" | "tilesets" | "timelines">[];
/**
* Delete a folder recursively. Only allowed if there are no assets
* in this or any subfolder.
*/
deleteFolder(path: string | string[]): Promise<void>;
/**
* Rename an existing folder. Allows for renaming any part of
* the path (useful both "moving" and "renaming" a folder).
* Array inputs are interpreted as pre-split paths. If the new
* name matches an existing folder, it will in effect be "merged"
* with that existing folder.
*
* Returns the list of folders and assets that are now in a new
* location. */
renameFolder(oldPath: string | string[], newPath: string | string[]): Promise<{
movedFolders: [from: {
name: string;
folderPath: string;
resourceType: "GMFolder";
resourceVersion: string;
tags?: string[] | undefined;
order?: number | undefined;
}, to: {
name: string;
folderPath: string;
resourceType: "GMFolder";
resourceVersion: string;
tags?: string[] | undefined;
order?: number | undefined;
} | undefined][];
movedAssets: Asset<"animcurves" | "extensions" | "fonts" | "notes" | "objects" | "particles" | "paths" | "rooms" | "roomui" | "scripts" | "sequences" | "shaders" | "sounds" | "sprites" | "tilesets" | "timelines">[];
} | undefined>;
/**
* Add a folder to the yyp file. The string can include separators,
* in which case nested folders will be created. If an array is provided,
* it is interpreted as a pre-split path. */
createFolder(path: string | string[], options?: {
skipSave?: boolean;
}): Promise<YypFolder | undefined>;
saveYyp(): Promise<void>;
/**
* The name of a resource, *in lower case*, from
* a path. This is used as the key for looking up resources.
*
* The path can be to the asset's folder, or to any file within
* that folder.
*/
assetNameFromPath(path: Pathy<any>): string;
/**
* When first creating an instance, we need to get all project file
* content into memory for fast access. In particular, we need all
* yyp, yy, and gml files for scripts and objects. For other asset types
* we just need their names and yyp filepaths.
*
* Can be called at any time -- will only operate on new assets.
*
* Returns the list of added assets. Assets are instanced and registered but their
* code is not parsed!
*/
protected loadAssets(options?: ProjectOptions): Promise<Asset[]>;
protected loadHelpLinks(): Promise<void>;
reloadConfig(): Promise<{
[x: string]: unknown;
$schema: "https://raw.githubusercontent.com/bscotch/stitch/develop/packages/config/schemas/stitch.config.schema.json";
textureGroupAssignments?: Record<string, string> | undefined;
audioGroupAssignments?: Record<string, string> | undefined;
runtimeVersion?: string | undefined;
newSpriteRules?: {
[x: string]: unknown;
allowedNames?: string[] | undefined;
} | undefined;
newSoundRules?: {
[x: string]: unknown;
allowedNames?: string[] | undefined;
defaults?: Record<string, {
mono?: boolean | undefined;
}> | undefined;
} | undefined;
gameConsoleStyle?: {
[x: string]: unknown;
base?: string | undefined;
lines?: {
[x: string]: unknown;
pattern: string;
base?: string | undefined;
description?: string | undefined;
caseSensitive?: boolean | undefined;
styles?: {
[x: string]: string;
} | undefined;
}[] | undefined;
} | undefined;
}>;
getWindowsName(): Promise<string | undefined>;
/**
* Load the GML spec for the project's runtime version, falling
* back on the included spec if necessary.
*/
protected loadGmlSpec(): Promise<void>;
/**
* Call to reload the project's yyp file (e.g. because it has changed
* on disk) and add/remove any resources.
*/
reloadYyp(): Promise<void>;
/**
* @internal
* Initialize a collection of new assets by parsing their GML */
initiallyParseAssetCode(assets: Asset[]): void;
protected initialize(options?: ProjectOptions): Promise<void>;
/**
* Create a new project instance and initialize it.
*/
static initialize(yypPath: string, options?: ProjectOptions): Promise<Project>;
static readonly fallbackGmlSpecPath: Pathy<unknown>;
}
//# sourceMappingURL=project.d.ts.map