/** * Model Classifier Utility * Determines whether a D365 F&O model is custom or standard * * Logic: * - Custom models are defined in CUSTOM_MODELS environment variable * - Supports wildcards: Custom*, *Test, *Extension* * - Models with EXTENSION_PREFIX are considered custom * - Auto-detected models from workspace are automatically registered as custom * - All other models are considered Microsoft standard models */ /** * Register a model as custom (e.g., from auto-detection) * This allows dynamically detected models to be treated as custom */ export declare function registerCustomModel(modelName: string): void; /** * Clear all auto-detected custom models (for test isolation) */ export declare function clearAutoDetectedModels(): void; /** * Get list of custom models from environment */ export declare function getCustomModels(): string[]; /** * Get extension prefix from environment */ export declare function getExtensionPrefix(): string; /** * Get configurable object suffix from environment. * Returns the raw EXTENSION_SUFFIX value (trailing underscores stripped). * Empty string when not configured. */ export declare function getObjectSuffix(): string; /** * True when the operator chose NO prefix at all (naming.prefixSource=none, env * EXTENSION_PREFIX_SOURCE=none) — the setting for suffix-based conventions * (issue #1085). New objects, fields and methods then keep the name as given: * nothing is inferred from the model, EXTENSION_PREFIX is ignored, and the model * name is NOT used as a fallback prefix. Extension elements and classes still * need a collision-avoiding token, so they embed the model name (the Visual * Studio default) — see getExtensionNamingStyle / getExtensionClassNamingStyle. */ export declare function isNoPrefixMode(): boolean; /** * Resolve the extension-naming style from the environment. * * - 'prefix' (default): extension elements and extension classes embed the * EXTENSION_PREFIX as an infix, per Microsoft's prefix-based guideline * (e.g. CustTable.CrExtension, CustTableCr_Extension). * * - 'model-name': extension elements and extension classes embed the MODEL NAME, * matching the Visual Studio developer-tools default * (e.g. CustTable.ContosoRobotics, CustTable_ContosoRobotics_Extension). * EXTENSION_PREFIX still applies to NEW objects and to fields/methods added * inside extensions — only the extension element/class token changes. * * Configured via EXTENSION_NAMING_STYLE. Any value other than 'model-name' * (including unset) resolves to 'prefix' so existing setups are unchanged. */ export declare function getExtensionNamingStyle(): 'prefix' | 'model-name'; /** * The same choice, for extension CLASSES only. * * One style drove both halves of the naming, and a convention that spells them * differently could not be expressed at all. It is not a hypothetical shape: a * model whose element extensions are the Visual Studio `CustTable.ContosoRobotics` * while its CoC classes are prefix-first `CtsoCustTableTbl_Extension` has to pick * one and break the other — 'model-name' rewrites the class to * `CtsoCustTableTbl_ContosoRobotics_Extension`, 'prefix' rewrites the element to * `CustTable.CtsoExtension`. Splitting the knob is the whole fix; each branch of * applyObjectPrefix now asks the question that belongs to it. * * Configured via EXTENSION_CLASS_NAMING_STYLE. Unset — or any value that is * neither 'prefix', 'model-name' nor 'prefix-leading' — inherits * getExtensionNamingStyle(), so a setup that never sets it behaves exactly as * before. * * 'prefix-leading' is the one shape 'prefix' cannot express: both put the * extension infix on the class, but 'prefix' trails it (`CustTableCtso_Extension`, * the shape shipped code and the element style use) while a convention such as * the Avanade D365FO Development Guidelines leads with it instead * (`CtsoCustTable_Extension`). Element extensions have no equivalent option — * `Base.{Infix}Extension` is Microsoft's own dot-notation shape and leading the * infix there would not parse as an AOT name. */ export declare function getExtensionClassNamingStyle(): 'prefix' | 'model-name' | 'prefix-leading'; /** * The naming-style variables that hold a value neither getter above recognises. * * Both getters fall back without a word — getExtensionNamingStyle to 'prefix', * getExtensionClassNamingStyle to inheriting it — so a typo such as "prefx" or * "modelname" silently means the default, and every name written afterwards * follows a convention nobody chose. get_workspace_info renders these lines. */ export declare function unrecognisedNamingStyleSettings(): string[]; /** * Apply a configurable suffix to a NEW model element name. * The suffix is appended at the end of the object name. * * Suffix does NOT apply to: * - Dot-notation extension elements (CustTable.XyExtension — suffix breaks MS naming) * - Extension classes ending with _Extension (SalesFormLetterXy_Extension) * - Names that already end with the suffix (case-insensitive) * * Examples with EXTENSION_SUFFIX="ZZ": * MyTable → MyTableZZ * MyClass → MyClassZZ * MyTableZZ → MyTableZZ (no double-suffix) * CustTable.XyExtension → CustTable.XyExtension (skip) * CustTableXy_Extension → CustTableXy_Extension (skip) */ export declare function applyObjectSuffix(objectName: string, suffix: string): string; /** * Resolve the clean prefix to use when naming newly created D365FO objects. * * Microsoft naming guidelines (https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/extensibility/naming-guidelines-extensions): * - New model elements → prefix concatenated directly: {Prefix}{ObjectName} (e.g. WHSMyTable) * - Extension elements → {BaseElement}.{Prefix}Extension (e.g. HCMWorker.WHSExtension) * - Extension classes → {BaseElement}{Prefix}_Extension (e.g. SalesFormLetterContoso_Extension) * - Fields in extensions→ {Prefix}{FieldName} (e.g. WHSApprovingWorker) * * Returns the prefix with any trailing '_' stripped — the underscore is not part * of the prefix itself, only of the form used for regular objects. See * resolveRawPrefix() for the priority order, and resolveRegularObjectPrefixToken() * for the token actually prepended to a name. * * Returns empty string when nothing resolves. */ export declare function resolveObjectPrefix(modelName: string): string; /** * Derive the extension infix form from an already-resolved prefix. * * Extension elements (dot-notation and _Extension classes) embed the prefix * as a PascalCase infix WITHOUT any underscore separator: * - Underscore-style EXTENSION_PREFIX "XY_" → resolved prefix "XY" → infix "Xy" * - Normal prefix "Contoso" → resolved prefix "Contoso" → infix "Contoso" * - All-caps prefix "WHS" with "WHS_" in env → infix "Whs" * - All-caps prefix "WHS" with "WHS" in env → infix "WHS" (unchanged) * - Compound "ConFinSK_" → infix "ConFinSk" (per segment) * * Detection: if the winning raw prefix ends with '_', lower each PascalCase * segment on its own (see toExtensionInfixCase) — flattening the whole token * would turn "ConFinSK" into "Confinsk". * * When `modelName` is given and that model's own extensions already state their * infix, it is used verbatim instead of being derived. The two are genuinely * independent: model Demo names regular objects "DEMO_Foo" but its extensions * "AssetBookTable.DEMOExtension" — deriving would produce "DemoExtension" and * every name would silently diverge from the model's existing convention. */ export declare function deriveExtensionInfix(resolvedPrefix: string, modelName?: string): string; /** * Resolve the literal prefix TOKEN that `applyObjectPrefix` prepends to a * REGULAR (non-extension) new object name for the CURRENT session — i.e. the * exact substring that will appear at the start of e.g. an EDT/table/class * Name in generated metadata (`{token}{ObjectName}`). * * Factored out of `applyObjectPrefix`'s "regular objects" branch (same * underscore-style-vs-PascalCase derivation) so other callers that need to * recognise/strip this token from ALREADY-GENERATED names — notably the eval * golden oracle's prefix-agnostic comparison (src/eval/oracle/normalize.ts, * see docs/AGENT_EVAL_LOOP.md §6.2) — don't have to duplicate the branching * logic. Returns '' when no prefix is configured. */ export declare function resolveRegularObjectPrefixToken(modelName?: string): string; export interface ApplyObjectPrefixOptions { /** * The text before "_Extension" is known to be the BASE class name, not a name * that may already carry the infix — e.g. normalizeObjectName was handed a bare * base for objectType "class-extension". Every class style reads it: a stated * base is never mistaken for one already carrying the infix or model token. */ knownBase?: boolean; /** * The object the class extends, read from its `[ExtensionOf(...)]` attribute. * With it, a stem that is that base followed by this model's token as its own * word — SalesTable|Mcp|CreditHold — is recognised as already prefixed and kept, * whatever follows the token. Without it, the token is only looked for at either * end of the stem, and SalesTableMcpCreditHold_Extension became * SalesTableMcpCreditHoldMcp_Extension: the name the caller asked for could not * be written at all, and a delete + re-create renamed it again. */ extendedBase?: string; } /** * Does `stem` start with `base` and continue with one of `tokens` as its own word * (an optional "_" before it, and an uppercase letter, "_" or the end after it)? * The base is compared case-insensitively, as X++ names are. */ export declare function tokenFollowsBase(stem: string, base: string, tokens: string[]): boolean; /** * The base a `{Base}{Token}{Feature}` extension-class stem names, for a caller * that has no `[ExtensionOf]` to read it from (validate_object_naming, prepare). * Each place a token starts as its own word inside the stem splits it into a * candidate base; the longest candidate `isKnownObject` accepts wins. Undefined * when none is known — the stem is then left to the end-of-stem rules. * * Also undefined when the WHOLE stem is a known object: then it is the base, and * the token inside it is part of that base's own name. AgreementAutolinkHelper and * AgreementAutolinkHelperSalesLine both ship, so under the token "Sales" the shorter * one read as the base, the bare AgreementAutolinkHelperSalesLine_Extension as * already prefixed — and prepare predicted a name create (which reads the real base * from [ExtensionOf]) does not write. */ export declare function inferBaseBeforeToken(stem: string, tokens: string[], isKnownObject: (name: string) => boolean): string | undefined; /** * Does `name` start with `infix` as its own PascalCase word? The character after * the infix must be an uppercase letter: "CtsoCustTable" leads with "Ctso", while * "ContactPerson" does not lead with "Con" (Con|t… is one word, not a prefix). * The infix itself compares case-insensitively, so "CTSOCustTable" also counts. */ export declare function leadsWithInfix(name: string, infix: string): boolean; /** * Does `name` end with `infix` as its own PascalCase word — the shape the trailing * 'prefix' class style writes before "_Extension" (CustTableCtso)? The matched * segment must START with an uppercase letter, so "SalesTable" does not end with * the infix "Le" and "SalesLine" does not end with "Ne": those are the last * letters of a word, and stripping them would cut the base name. */ export declare function endsWithInfix(name: string, infix: string): boolean; /** * The base class a prefix-leading extension-class stem (the part before * "_Extension") names — the ONE derivation the writer (applyObjectPrefix) and the * validator (checkObjectNaming) share, so the two cannot disagree on it. * * CtsoCustTable → CustTable (infix leads — already prefix-leading) * CustTableCtso → CustTable (stale trailing infix from the 'prefix' style) * SalesTable, "Le" → SalesTable (not a word boundary — nothing to strip) * * Ambiguous by construction: a base that itself starts with the infix as a word * ("ProjTable" under prefix "Proj") reads as already prefixed. Callers that KNOW * the base pass it through ApplyObjectPrefixOptions.knownBase instead. */ export declare function prefixLeadingBaseOf(stem: string, infix: string): string; /** * `{Infix}{Base}_Extension`. The base's first letter is upper-cased: the shipped * camelCase classes (whsWorkExecute…) would otherwise give "CtsowhsWorkExecute", * whose infix no longer ends at a word boundary — so the name could not be * recognised as prefixed again and every re-run grew another "Ctso". X++ names * are case-insensitive, so the class still extends the same base. */ export declare function prefixLeadingClassName(base: string, infix: string): string; /** * The base class an `_Extension` class name extends, under whichever class style * wrote it — CustTableCtso_Extension, CtsoCustTable_Extension and * CustTable_ContosoExt_Extension all give CustTable. Tokens are only taken off as * their own PascalCase word (endsWithInfix / leadsWithInfix), so SalesTable stays * SalesTable under a prefix "Le". A name not ending in "_Extension" is returned * as given. Best effort for a name nobody stated the base of: a base that itself * begins or ends with the token as a word is indistinguishable from a prefixed one. */ export declare function extensionClassBaseOf(name: string, modelName?: string): string; /** * Apply prefix to a NEW model element name. * Per MS guidelines, the prefix is concatenated directly (no separator): * WHSMyTable, MyPrefixMyClass, ContosoMyForm * * Underscore-style prefixes (EXTENSION_PREFIX="XY_") are handled specially: * - Regular objects (classes, tables, forms, …): prefix kept with underscore * XY_CustTable, XY_MyClass (NOT XyCustTable) * - Extension elements (dot-notation or _Extension class infix): PascalCase, no underscore * CustTable.XyExtension, CustTableXy_Extension (NOT CustTable.XY_Extension) * * CRITICAL for extension classes: If EXTENSION_PREFIX is set in .env, * it should be used EXCLUSIVELY - never combined with modelName prefix. * The function receives the ALREADY RESOLVED prefix (from resolveObjectPrefix), * so it strips any existing suffix-prefix and replaces it with the current one. * * Case-insensitive check prevents double-prefixing. * * ALWAYS pass `modelName` when you know it. Omitting it does not merely lose the * model-name naming style — it changes the regular-object result, because the raw * prefix then falls back to EXTENSION_PREFIX and the model's own separator is * invisible: a model whose objects are "ConSK_*" yields "ConSK_QualityTier" * with the argument and "ConSKQualityTier" without it. prepare(mode="create") * predicted names through the 2-arg form while d365fo_file(action="create") wrote * them through the 3-arg form, so the two disagreed on every underscore-style model. * Prefer normalizeObjectName() (utils/objectNaming.ts), which is the one path * create/modify already share. */ export declare function applyObjectPrefix(objectName: string, prefix: string, modelName?: string, options?: ApplyObjectPrefixOptions): string; /** * Check if a model is custom (case-insensitive) * @param modelName - Name of the model to check * @returns true if model is custom, false if standard */ export declare function isCustomModel(modelName: string): boolean; /** * Check if a model is standard (opposite of custom) * @param modelName - Name of the model to check * @returns true if model is standard Microsoft model */ export declare function isStandardModel(modelName: string): boolean; //# sourceMappingURL=modelClassifier.d.ts.map