From bceba7f6106c26c938f281c298182d5e0213e27f Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Thu, 27 Apr 2023 16:00:21 -0500 Subject: [PATCH 01/11] fix: offer suggestions for unresolved metadata types --- package.json | 4 +- src/registry/registryAccess.ts | 32 +++- src/registry/types.ts | 2 +- src/resolve/metadataResolver.ts | 87 ++++++++- test/nuts/suggestType/suggestType.nut.ts | 87 +++++++++ .../testProj/config/project-scratch-def.json | 13 ++ ...ect__c-MyTestObject Layout.Layout-meta.xml | 55 ++++++ .../MyTestObject__c.objct-meta.xml | 165 ++++++++++++++++++ .../default/tabs/Settings.tabsss-meta.xml | 7 + .../testProj/package-manifest/package.xml | 8 + .../suggestType/testProj/sfdx-project.json | 12 ++ yarn.lock | 5 + 12 files changed, 472 insertions(+), 5 deletions(-) create mode 100644 test/nuts/suggestType/suggestType.nut.ts create mode 100644 test/nuts/suggestType/testProj/config/project-scratch-def.json create mode 100644 test/nuts/suggestType/testProj/force-app/main/default/layouts/MyTestObject__c-MyTestObject Layout.Layout-meta.xml create mode 100644 test/nuts/suggestType/testProj/force-app/main/default/objects/MyTestObject__c/MyTestObject__c.objct-meta.xml create mode 100644 test/nuts/suggestType/testProj/force-app/main/default/tabs/Settings.tabsss-meta.xml create mode 100644 test/nuts/suggestType/testProj/package-manifest/package.xml create mode 100644 test/nuts/suggestType/testProj/sfdx-project.json diff --git a/package.json b/package.json index 99a7b9b3fc..43719fce90 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@salesforce/kit": "^1.9.2", "@salesforce/ts-types": "^1.7.2", "archiver": "^5.3.1", + "fast-levenshtein": "^3.0.0", "fast-xml-parser": "^4.1.4", "got": "^11.8.6", "graceful-fs": "^4.2.11", @@ -47,6 +48,7 @@ "@salesforce/ts-sinon": "^1.4.6", "@types/archiver": "^5.3.1", "@types/deep-equal-in-any-order": "^1.0.1", + "@types/fast-levenshtein": "^0.0.2", "@types/mime": "2.0.3", "@types/minimatch": "^5.1.2", "@types/proxy-from-env": "^1.0.1", @@ -187,4 +189,4 @@ "output": [] } } -} \ No newline at end of file +} diff --git a/src/registry/registryAccess.ts b/src/registry/registryAccess.ts index ce98853200..02f7c415a9 100644 --- a/src/registry/registryAccess.ts +++ b/src/registry/registryAccess.ts @@ -5,6 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import { Messages, SfError } from '@salesforce/core'; +import * as Levenshtein from 'fast-levenshtein'; import { registry as defaultRegistry } from './registry'; import { MetadataRegistry, MetadataType } from './types'; @@ -60,12 +61,41 @@ export class RegistryAccess { * @returns The corresponding metadata type object */ public getTypeBySuffix(suffix: string): MetadataType | undefined { - if (this.registry.suffixes?.[suffix]) { + if (this.registry.suffixes[suffix]) { const typeId = this.registry.suffixes[suffix]; return this.getTypeByName(typeId); } } + /** + * Find similar metadata type matches by its file suffix + * + * @param suffix - File suffix of the metadata type + * @returns An array of similar suffix and metadata type matches + */ + public guessTypeBySuffix( + suffix: string + ): Array<{ suffixGuess: string; metadataTypeGuess: MetadataType }> | undefined { + const registryKeys = Object.keys(this.registry.suffixes); + + const scores = registryKeys.map((registryKey) => ({ registryKey, score: Levenshtein.get(suffix, registryKey) })); + const sortedScores = scores.sort((a, b) => a.score - b.score); + const lowestScore = sortedScores[0].score; + // Levenshtein uses positive integers for scores, find all scores that match the lowest score + const guesses = sortedScores.filter((score) => score.score === lowestScore); + + if (guesses.length > 0) { + return guesses.map((guess) => { + const typeId = this.registry.suffixes[guess.registryKey]; + const metadataType = this.getTypeByName(typeId); + return { + suffixGuess: guess.registryKey, + metadataTypeGuess: metadataType, + }; + }); + } + } + /** * Searches for the first metadata type in the registry that returns `true` * for the given predicate function. diff --git a/src/registry/types.ts b/src/registry/types.ts index e7714946d5..506dede45d 100644 --- a/src/registry/types.ts +++ b/src/registry/types.ts @@ -11,7 +11,7 @@ */ export interface MetadataRegistry { types: TypeIndex; - suffixes?: SuffixIndex; + suffixes: SuffixIndex; strictDirectoryNames: { [directoryName: string]: string; }; diff --git a/src/resolve/metadataResolver.ts b/src/resolve/metadataResolver.ts index c41f3ab1e5..4077a7a9ce 100644 --- a/src/resolve/metadataResolver.ts +++ b/src/resolve/metadataResolver.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import { basename, dirname, join, sep } from 'path'; -import { Lifecycle, Messages, SfError } from '@salesforce/core'; +import { Lifecycle, Messages, SfError, Logger } from '@salesforce/core'; import { extName, parentName, parseMetadataXml } from '../utils'; import { MetadataType, RegistryAccess } from '../registry'; import { ComponentSet } from '../collections'; @@ -25,6 +25,7 @@ const messages = Messages.loadMessages('@salesforce/source-deploy-retrieve', 'sd */ export class MetadataResolver { public forceIgnoredPaths: Set; + protected logger: Logger; private forceIgnore?: ForceIgnore; private sourceAdapterFactory: SourceAdapterFactory; private folderContentTypeDirNames?: string[]; @@ -38,6 +39,7 @@ export class MetadataResolver { private tree: TreeContainer = new NodeFSTreeContainer(), private useFsForceIgnore = true ) { + this.logger = Logger.childFromRoot(this.constructor.name); this.sourceAdapterFactory = new SourceAdapterFactory(this.registry, tree); this.forceIgnoredPaths = new Set(); } @@ -145,7 +147,19 @@ export class MetadataResolver { function: 'resolveComponent', path: fsPath, }); - throw new SfError(messages.getMessage('error_could_not_infer_type', [fsPath]), 'TypeInferenceError'); + + // If a file ends with .xml and is not a metadata type, it is likely a package manifest + // In the past, these were "resolved" as EmailServicesFunction. See note on "attempt 3" in resolveType() below. + if (fsPath.endsWith('.xml') && !fsPath.endsWith(META_XML_SUFFIX)) { + this.logger.debug(`Could not resolve type for ${fsPath}. It is likely a package manifest. Moving on.`); + return undefined; + } + + // The metadata type could not be inferred + // Attempt to guess the type and throw an error with actions + const actions = this.getSuggestionsForUnresolvedTypes(fsPath); + + throw new SfError(messages.getMessage('error_could_not_infer_type', [fsPath]), 'TypeInferenceError', actions); } private resolveTypeFromStrictFolder(fsPath: string): MetadataType | undefined { @@ -202,6 +216,15 @@ export class MetadataResolver { // attempt 3 - try treating the file extension name as a suffix if (!resolvedType) { resolvedType = this.registry.getTypeBySuffix(extName(fsPath)); + + // Metadata types with `strictDirectoryName` should have been caught in "attempt 1". + // If the metadata returned from this lookup has a `strictDirectoryName`, something is wrong. + // It is likely that the metadata file is misspelled or has the wrong suffix. + // A common occurrence is that a misspelled metadata file will fall back to + // `EmailServicesFunction` because that is the default for the `.xml` suffix + if (resolvedType?.strictDirectoryName === true) { + resolvedType = undefined; + } } // attempt 4 - try treating the content as metadata @@ -215,6 +238,66 @@ export class MetadataResolver { return resolvedType; } + /** + * Attempt to find similar types for types that could not be inferred + * To be used after executing the resolveType() method + * + * @param fsPath + * @returns an array of suggestions + */ + private getSuggestionsForUnresolvedTypes(fsPath: string): string[] { + const actions = []; + + const parsedMetaXml = parseMetadataXml(fsPath); + + // Analogous to "attempt 2" above + // Attempt to guess the metadata suffix by finding a close match + if (parsedMetaXml?.suffix) { + const results = this.registry.guessTypeBySuffix(parsedMetaXml.suffix); + + if (results) { + actions.push( + `A search for the ".${parsedMetaXml.suffix}-meta.xml" metadata suffix found the following close match${ + results.length > 1 ? 'es' : '' + }:` + ); + results.forEach((result) => { + actions.push( + `- Did you mean ".${result.suffixGuess}-meta.xml" instead for the "${result.metadataTypeGuess.name}" metadata type?` + ); + }); + // check the file name, check the extension, check the folder and here is the registry + } + } + + // Analogous to "attempt 3" above + // Attempt to guess the filename suffix by finding a close match + if (actions.length === 0 && !fsPath.includes('-meta.xml')) { + const fileExtension = extName(fsPath); + const results = this.registry.guessTypeBySuffix(fileExtension); + if (results) { + actions.push( + `A search for the ".${fileExtension}" filename suffix found the following close match${ + results.length > 1 ? 'es' : '' + }:` + ); + results.forEach((result) => { + actions.push( + `- Did you mean ".${result.suffixGuess}" instead for the "${result.metadataTypeGuess.name}" metadata type?` + ); + }); + } + } + + if (actions.length > 0) { + actions.push( + '\nAdditional suggestions:\nConfirm the file name, extension, and directory names are correct. Validate against the registry at:\nhttps://github.com/forcedotcom/source-deploy-retrieve/blob/main/src/registry/metadataRegistry.json' + ); + } + + return actions; + } + /** * Whether or not a directory that represents a single component should be resolved as one, * or if it should be walked for additional components. diff --git a/test/nuts/suggestType/suggestType.nut.ts b/test/nuts/suggestType/suggestType.nut.ts new file mode 100644 index 0000000000..983dfcb5bc --- /dev/null +++ b/test/nuts/suggestType/suggestType.nut.ts @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import * as path from 'path'; +import { TestSession } from '@salesforce/cli-plugins-testkit'; +import { expect } from 'chai'; +import { SfError } from '@salesforce/core'; +import { ComponentSetBuilder } from '../../../src'; + +describe('suggest types', () => { + let session: TestSession; + + before(async () => { + session = await TestSession.create({ + project: { + sourceDir: path.join('test', 'nuts', 'suggestType', 'testProj'), + }, + devhubAuthStrategy: 'NONE', + }); + }); + + after(async () => { + await session?.clean(); + }); + + it('it offers a suggestions on an invalid type', async () => { + try { + await ComponentSetBuilder.build({ + sourcepath: [path.join(session.project.dir, 'force-app', 'main', 'default', 'objects')], + }); + throw new Error('This test should have thrown'); + } catch (err) { + const error = err as SfError; + expect(error.name).to.equal('TypeInferenceError'); + expect(error.actions).to.include( + 'A search for the ".objct-meta.xml" metadata suffix found the following close match:' + ); + expect(error.actions).to.include( + '- Did you mean ".object-meta.xml" instead for the "CustomObject" metadata type?' + ); + } + }); + + it('it offers a suggestions on a incorrect casing', async () => { + try { + await ComponentSetBuilder.build({ + sourcepath: [path.join(session.project.dir, 'force-app', 'main', 'default', 'layouts')], + }); + throw new Error('This test should have thrown'); + } catch (err) { + const error = err as SfError; + expect(error.name).to.equal('TypeInferenceError'); + expect(error.actions).to.include( + 'A search for the ".Layout-meta.xml" metadata suffix found the following close match:' + ); + expect(error.actions).to.include('- Did you mean ".layout-meta.xml" instead for the "Layout" metadata type?'); + } + }); + + it('it offers multiple suggestions if Levenshtein distance is the same', async () => { + try { + await ComponentSetBuilder.build({ + sourcepath: [path.join(session.project.dir, 'force-app', 'main', 'default', 'tabs')], + }); + throw new Error('This test should have thrown'); + } catch (err) { + const error = err as SfError; + expect(error.name).to.equal('TypeInferenceError'); + expect(error.actions).to.include( + 'A search for the ".tabsss-meta.xml" metadata suffix found the following close matches:' + ); + expect(error.actions).to.include( + '- Did you mean ".labels-meta.xml" instead for the "CustomLabels" metadata type?' + ); + expect(error.actions).to.include('- Did you mean ".tab-meta.xml" instead for the "CustomTab" metadata type?'); + } + }); + + it('it ignores package manifest files', async () => { + const cs = await ComponentSetBuilder.build({ sourcepath: [path.join(session.project.dir, 'package-manifest')] }); + expect(cs['components'].size).to.equal(0); + }); +}); diff --git a/test/nuts/suggestType/testProj/config/project-scratch-def.json b/test/nuts/suggestType/testProj/config/project-scratch-def.json new file mode 100644 index 0000000000..c8b8be34c3 --- /dev/null +++ b/test/nuts/suggestType/testProj/config/project-scratch-def.json @@ -0,0 +1,13 @@ +{ + "orgName": "ewillhoit company", + "edition": "Developer", + "features": ["EnableSetPasswordInApi"], + "settings": { + "lightningExperienceSettings": { + "enableS1DesktopEnabled": true + }, + "mobileSettings": { + "enableS1EncryptedStoragePref2": false + } + } +} diff --git a/test/nuts/suggestType/testProj/force-app/main/default/layouts/MyTestObject__c-MyTestObject Layout.Layout-meta.xml b/test/nuts/suggestType/testProj/force-app/main/default/layouts/MyTestObject__c-MyTestObject Layout.Layout-meta.xml new file mode 100644 index 0000000000..a4c98b2f54 --- /dev/null +++ b/test/nuts/suggestType/testProj/force-app/main/default/layouts/MyTestObject__c-MyTestObject Layout.Layout-meta.xml @@ -0,0 +1,55 @@ + + + + false + false + true + + + + Required + Name + + + + + Edit + OwnerId + + + + + + false + false + true + + + + Readonly + CreatedById + + + + + Readonly + LastModifiedById + + + + + + false + false + true + + + + + + false + false + false + false + false + diff --git a/test/nuts/suggestType/testProj/force-app/main/default/objects/MyTestObject__c/MyTestObject__c.objct-meta.xml b/test/nuts/suggestType/testProj/force-app/main/default/objects/MyTestObject__c/MyTestObject__c.objct-meta.xml new file mode 100644 index 0000000000..28db778254 --- /dev/null +++ b/test/nuts/suggestType/testProj/force-app/main/default/objects/MyTestObject__c/MyTestObject__c.objct-meta.xml @@ -0,0 +1,165 @@ + + + + Accept + Default + + + Accept + Large + Default + + + Accept + Small + Default + + + CancelEdit + Default + + + CancelEdit + Large + Default + + + CancelEdit + Small + Default + + + Clone + Default + + + Clone + Large + Default + + + Clone + Small + Default + + + Delete + Default + + + Delete + Large + Default + + + Delete + Small + Default + + + Edit + Default + + + Edit + Large + Default + + + Edit + Small + Default + + + List + Default + + + List + Large + Default + + + List + Small + Default + + + New + Default + + + New + Large + Default + + + New + Small + Default + + + SaveEdit + Default + + + SaveEdit + Large + Default + + + SaveEdit + Small + Default + + + Tab + Default + + + Tab + Large + Default + + + Tab + Small + Default + + + View + Default + + + View + Large + Default + + + View + Small + Default + + false + SYSTEM + Deployed + false + true + false + false + false + false + false + true + true + Private + + + + Text + + MyTestObject + + ReadWrite + Public + diff --git a/test/nuts/suggestType/testProj/force-app/main/default/tabs/Settings.tabsss-meta.xml b/test/nuts/suggestType/testProj/force-app/main/default/tabs/Settings.tabsss-meta.xml new file mode 100644 index 0000000000..e93d3c8faf --- /dev/null +++ b/test/nuts/suggestType/testProj/force-app/main/default/tabs/Settings.tabsss-meta.xml @@ -0,0 +1,7 @@ + + + Created by Lightning App Builder + Settings + + Custom19: Wrench + diff --git a/test/nuts/suggestType/testProj/package-manifest/package.xml b/test/nuts/suggestType/testProj/package-manifest/package.xml new file mode 100644 index 0000000000..e4f345f282 --- /dev/null +++ b/test/nuts/suggestType/testProj/package-manifest/package.xml @@ -0,0 +1,8 @@ + + + + label_one + CustomLabel + + 55.0 + \ No newline at end of file diff --git a/test/nuts/suggestType/testProj/sfdx-project.json b/test/nuts/suggestType/testProj/sfdx-project.json new file mode 100644 index 0000000000..0cb02e4e82 --- /dev/null +++ b/test/nuts/suggestType/testProj/sfdx-project.json @@ -0,0 +1,12 @@ +{ + "packageDirectories": [ + { + "path": "force-app", + "default": true + } + ], + "name": "testProj", + "namespace": "", + "sfdcLoginUrl": "https://login.salesforce.com", + "sourceApiVersion": "57.0" +} diff --git a/yarn.lock b/yarn.lock index 9df0e38364..e22ae4ce9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1126,6 +1126,11 @@ resolved "https://registry.yarnpkg.com/@types/expect/-/expect-1.20.4.tgz#8288e51737bf7e3ab5d7c77bfa695883745264e5" integrity sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg== +"@types/fast-levenshtein@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@types/fast-levenshtein/-/fast-levenshtein-0.0.2.tgz#9f618cff4469da8df46c9ee91b1c95b9af1d8f6a" + integrity sha512-h9AGeNlFimLtFUlEZgk+hb3LUT4tNHu8y0jzCUeTdi1BM4e86sBQs/nQYgHk70ksNyNbuLwpymFAXkb0GAehmw== + "@types/glob@~7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" From ef890261f519c9989d1fdc15a796d3d8ebbd6684 Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Fri, 28 Apr 2023 10:19:08 -0500 Subject: [PATCH 02/11] chore: bump core --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 43719fce90..1b139bc8f0 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "node": ">=14.0.0" }, "dependencies": { - "@salesforce/core": "^3.34.8", + "@salesforce/core": "^3.34.9", "@salesforce/kit": "^1.9.2", "@salesforce/ts-types": "^1.7.2", "archiver": "^5.3.1", diff --git a/yarn.lock b/yarn.lock index e22ae4ce9f..4596f66416 100644 --- a/yarn.lock +++ b/yarn.lock @@ -890,10 +890,10 @@ strip-ansi "6.0.1" ts-retry-promise "^0.7.0" -"@salesforce/core@^3.34.6", "@salesforce/core@^3.34.8": - version "3.34.8" - resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-3.34.8.tgz#01d74a6548ca395502ad61fe6e6e0ddc83f2ad01" - integrity sha512-2BpnyHMzN7NOg/nXEQNxpditMfQ9oKD30wC1IpTbSeS5JxZYYCAdDZkL0CfbtUceJwj/E3/x6oldiW2zQugrVg== +"@salesforce/core@^3.34.6", "@salesforce/core@^3.34.9": + version "3.34.9" + resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-3.34.9.tgz#3176fa71c32b2efe448840ed597c6f138557400e" + integrity sha512-+1U2ISyyQQwLR2a/0PeXB3ljm0Jw2Qk197EVIj48DPYav0rmidzb8pf45ZTRXkSVQkxUdzHp/X3w4FQFkDAbWg== dependencies: "@salesforce/bunyan" "^2.0.0" "@salesforce/kit" "^1.9.2" From 95584c7a8f61c514cabeb507016fd43ec9bac8e2 Mon Sep 17 00:00:00 2001 From: Shane McLaughlin Date: Mon, 1 May 2023 14:42:28 -0500 Subject: [PATCH 03/11] Sm/pr feedback 948 (#953) * fix: ut and better answers for case errors * test: another case guess test --- src/registry/registryAccess.ts | 5 ++++- test/registry/registryAccess.test.ts | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/registry/registryAccess.ts b/src/registry/registryAccess.ts index 02f7c415a9..2aaf9762ea 100644 --- a/src/registry/registryAccess.ts +++ b/src/registry/registryAccess.ts @@ -78,7 +78,10 @@ export class RegistryAccess { ): Array<{ suffixGuess: string; metadataTypeGuess: MetadataType }> | undefined { const registryKeys = Object.keys(this.registry.suffixes); - const scores = registryKeys.map((registryKey) => ({ registryKey, score: Levenshtein.get(suffix, registryKey) })); + const scores = registryKeys.map((registryKey) => ({ + registryKey, + score: Levenshtein.get(suffix, registryKey, { useCollator: true }), + })); const sortedScores = scores.sort((a, b) => a.score - b.score); const lowestScore = sortedScores[0].score; // Levenshtein uses positive integers for scores, find all scores that match the lowest score diff --git a/test/registry/registryAccess.test.ts b/test/registry/registryAccess.test.ts index e639027dd7..6d585e87b7 100644 --- a/test/registry/registryAccess.test.ts +++ b/test/registry/registryAccess.test.ts @@ -110,4 +110,15 @@ describe('RegistryAccess', () => { expect(registryAccess.getParentType(registry.types.digitalexperienceconfig.id)).to.be.undefined; }); }); + + describe('suggestions', () => { + it('guess for a type that is all uppercase should return the correct type first', () => { + const result = registryAccess.guessTypeBySuffix('CLS'); + expect(result?.[0].metadataTypeGuess.name).to.equal('ApexClass'); + }); + it('guess for a type that is first-uppercase should return the correct type first', () => { + const result = registryAccess.guessTypeBySuffix('Cls'); + expect(result?.[0].metadataTypeGuess.name).to.equal('ApexClass'); + }); + }); }); From f1158f6cd9456260ec3dd7b2a90a1732b53004e8 Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Tue, 2 May 2023 12:46:30 -0500 Subject: [PATCH 04/11] fix: pr feedback --- messages/sdr.md | 14 +++ package.json | 1 + src/resolve/metadataResolver.ts | 86 +++++++------------ test/nuts/suggestType/suggestType.nut.ts | 55 ++++++++++-- .../main/default/classes/DummyClass.clss | 4 + .../main/default/classes/MyEmailHandler.cls | 10 +++ .../classes/MyEmailHandler.cls-meta.xml | 6 ++ .../EmailServiceName.xml-meta.xml | 22 +++++ 8 files changed, 135 insertions(+), 63 deletions(-) create mode 100644 test/nuts/suggestType/testProj/force-app/main/default/classes/DummyClass.clss create mode 100644 test/nuts/suggestType/testProj/force-app/main/default/classes/MyEmailHandler.cls create mode 100644 test/nuts/suggestType/testProj/force-app/main/default/classes/MyEmailHandler.cls-meta.xml create mode 100644 test/nuts/suggestType/testProj/force-app/main/default/emailservices/EmailServiceName.xml-meta.xml diff --git a/messages/sdr.md b/messages/sdr.md index cb41b92705..e19df84828 100644 --- a/messages/sdr.md +++ b/messages/sdr.md @@ -157,3 +157,17 @@ No uniqueIdElement found in registry for %s (reading %s at %s). # uniqueIdElementNotInChild The uniqueIdElement %s was not found the child (reading %s at %s). + +# suggest_type_header + +A search for the %s suffix found the following close matches: + +# suggest_type_did_you_mean + +-- Did you mean ".%s%s" instead for the "%s" metadata type? + +# suggest_type_more_suggestions + +Additional suggestions: +Confirm the file name, extension, and directory names are correct. Validate against the registry at: +https://github.com/forcedotcom/source-deploy-retrieve/blob/main/src/registry/metadataRegistry.json diff --git a/package.json b/package.json index 1b139bc8f0..aa7621934f 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "test": "wireit", "test:nuts": "mocha \"test/nuts/local/**/*.nut.ts\" --timeout 500000", "test:nuts:scale": "mocha \"test/nuts/scale/eda.nut.ts\" --timeout 500000; mocha \"test/nuts/scale/lotsOfClasses.nut.ts\" --timeout 500000; mocha \"test/nuts/scale/lotsOfClassesOneDir.nut.ts\" --timeout 500000", + "test:nuts:suggest": "mocha \"test/nuts/suggestType/suggestType.nut.ts\" --timeout 10000", "test:only": "wireit", "test:registry": "mocha ./test/registry/registryCompleteness.test.ts --timeout 50000", "update-registry": "npx ts-node scripts/update-registry/update2.ts", diff --git a/src/resolve/metadataResolver.ts b/src/resolve/metadataResolver.ts index 4077a7a9ce..c3a5b13ec2 100644 --- a/src/resolve/metadataResolver.ts +++ b/src/resolve/metadataResolver.ts @@ -141,12 +141,6 @@ export class MetadataResolver { !adapter.allowMetadataWithContent(); return shouldResolve ? adapter.getComponent(fsPath, isResolvingSource) : undefined; } - void Lifecycle.getInstance().emitTelemetry({ - eventName: 'metadata_resolver_type_inference_error', - library: 'SDR', - function: 'resolveComponent', - path: fsPath, - }); // If a file ends with .xml and is not a metadata type, it is likely a package manifest // In the past, these were "resolved" as EmailServicesFunction. See note on "attempt 3" in resolveType() below. @@ -155,6 +149,13 @@ export class MetadataResolver { return undefined; } + void Lifecycle.getInstance().emitTelemetry({ + eventName: 'metadata_resolver_type_inference_error', + library: 'SDR', + function: 'resolveComponent', + path: fsPath, + }); + // The metadata type could not be inferred // Attempt to guess the type and throw an error with actions const actions = this.getSuggestionsForUnresolvedTypes(fsPath); @@ -246,56 +247,31 @@ export class MetadataResolver { * @returns an array of suggestions */ private getSuggestionsForUnresolvedTypes(fsPath: string): string[] { - const actions = []; - const parsedMetaXml = parseMetadataXml(fsPath); - - // Analogous to "attempt 2" above - // Attempt to guess the metadata suffix by finding a close match - if (parsedMetaXml?.suffix) { - const results = this.registry.guessTypeBySuffix(parsedMetaXml.suffix); - - if (results) { - actions.push( - `A search for the ".${parsedMetaXml.suffix}-meta.xml" metadata suffix found the following close match${ - results.length > 1 ? 'es' : '' - }:` - ); - results.forEach((result) => { - actions.push( - `- Did you mean ".${result.suffixGuess}-meta.xml" instead for the "${result.metadataTypeGuess.name}" metadata type?` - ); - }); - // check the file name, check the extension, check the folder and here is the registry - } - } - - // Analogous to "attempt 3" above - // Attempt to guess the filename suffix by finding a close match - if (actions.length === 0 && !fsPath.includes('-meta.xml')) { - const fileExtension = extName(fsPath); - const results = this.registry.guessTypeBySuffix(fileExtension); - if (results) { - actions.push( - `A search for the ".${fileExtension}" filename suffix found the following close match${ - results.length > 1 ? 'es' : '' - }:` - ); - results.forEach((result) => { - actions.push( - `- Did you mean ".${result.suffixGuess}" instead for the "${result.metadataTypeGuess.name}" metadata type?` - ); - }); - } - } - - if (actions.length > 0) { - actions.push( - '\nAdditional suggestions:\nConfirm the file name, extension, and directory names are correct. Validate against the registry at:\nhttps://github.com/forcedotcom/source-deploy-retrieve/blob/main/src/registry/metadataRegistry.json' - ); - } - - return actions; + const metaSuffix = parsedMetaXml?.suffix; + + // Analogous to "attempt 2" and "attempt 3" above + const guesses = metaSuffix + ? this.registry.guessTypeBySuffix(metaSuffix) + : this.registry.guessTypeBySuffix(extName(fsPath)); + + // If guesses were found, format an array of strings to be passed to SfError's actions + return guesses && guesses.length > 0 + ? [ + messages.getMessage('suggest_type_header', [ + metaSuffix ? `".${parsedMetaXml.suffix}-meta.xml" metadata` : `".${extName(fsPath)}" filename`, + ]), + ...guesses.map((guess) => + messages.getMessage('suggest_type_did_you_mean', [ + guess.suffixGuess, + metaSuffix ? '-meta.xml' : '', + guess.metadataTypeGuess.name, + ]) + ), + '', // A blank line makes this much easier to read (it doesn't seem to be possible to start a markdown message entry with a newline) + messages.getMessage('suggest_type_more_suggestions'), + ] + : []; } /** diff --git a/test/nuts/suggestType/suggestType.nut.ts b/test/nuts/suggestType/suggestType.nut.ts index 983dfcb5bc..320bad41f4 100644 --- a/test/nuts/suggestType/suggestType.nut.ts +++ b/test/nuts/suggestType/suggestType.nut.ts @@ -8,9 +8,12 @@ import * as path from 'path'; import { TestSession } from '@salesforce/cli-plugins-testkit'; import { expect } from 'chai'; -import { SfError } from '@salesforce/core'; +import { SfError, Messages } from '@salesforce/core'; import { ComponentSetBuilder } from '../../../src'; +Messages.importMessagesDirectory(__dirname); +const messages = Messages.loadMessages('@salesforce/source-deploy-retrieve', 'sdr'); + describe('suggest types', () => { let session: TestSession; @@ -27,7 +30,7 @@ describe('suggest types', () => { await session?.clean(); }); - it('it offers a suggestions on an invalid type', async () => { + it('it offers a suggestions on an invalid metadata suffix', async () => { try { await ComponentSetBuilder.build({ sourcepath: [path.join(session.project.dir, 'force-app', 'main', 'default', 'objects')], @@ -37,14 +40,28 @@ describe('suggest types', () => { const error = err as SfError; expect(error.name).to.equal('TypeInferenceError'); expect(error.actions).to.include( - 'A search for the ".objct-meta.xml" metadata suffix found the following close match:' + 'A search for the ".objct-meta.xml" metadata suffix found the following close matches:' ); expect(error.actions).to.include( - '- Did you mean ".object-meta.xml" instead for the "CustomObject" metadata type?' + '-- Did you mean ".object-meta.xml" instead for the "CustomObject" metadata type?' ); } }); + it('it offers a suggestions on an invalid filename suffix', async () => { + try { + await ComponentSetBuilder.build({ + sourcepath: [path.join(session.project.dir, 'force-app', 'main', 'default', 'classes', 'DummyClass.clss')], + }); + throw new Error('This test should have thrown'); + } catch (err) { + const error = err as SfError; + expect(error.name).to.equal('TypeInferenceError'); + expect(error.actions).to.include('A search for the ".clss" filename suffix found the following close matches:'); + expect(error.actions).to.include('-- Did you mean ".cls" instead for the "ApexClass" metadata type?'); + } + }); + it('it offers a suggestions on a incorrect casing', async () => { try { await ComponentSetBuilder.build({ @@ -55,9 +72,9 @@ describe('suggest types', () => { const error = err as SfError; expect(error.name).to.equal('TypeInferenceError'); expect(error.actions).to.include( - 'A search for the ".Layout-meta.xml" metadata suffix found the following close match:' + 'A search for the ".Layout-meta.xml" metadata suffix found the following close matches:' ); - expect(error.actions).to.include('- Did you mean ".layout-meta.xml" instead for the "Layout" metadata type?'); + expect(error.actions).to.include('-- Did you mean ".layout-meta.xml" instead for the "Layout" metadata type?'); } }); @@ -74,12 +91,34 @@ describe('suggest types', () => { 'A search for the ".tabsss-meta.xml" metadata suffix found the following close matches:' ); expect(error.actions).to.include( - '- Did you mean ".labels-meta.xml" instead for the "CustomLabels" metadata type?' + '-- Did you mean ".labels-meta.xml" instead for the "CustomLabels" metadata type?' ); - expect(error.actions).to.include('- Did you mean ".tab-meta.xml" instead for the "CustomTab" metadata type?'); + expect(error.actions).to.include('-- Did you mean ".tab-meta.xml" instead for the "CustomTab" metadata type?'); } }); + it('it offers additional suggestions to try', async () => { + try { + await ComponentSetBuilder.build({ + sourcepath: [path.join(session.project.dir, 'force-app', 'main', 'default', 'tabs')], + }); + throw new Error('This test should have thrown'); + } catch (err) { + const error = err as SfError; + expect(error.name).to.equal('TypeInferenceError'); + expect(error.actions).to.include(messages.getMessage('suggest_type_more_suggestions')); + } + }); + + // Since EmailServicesFunction uses the 'xml' suffix, we want to ensure it still resolves correctly + it('it still correctly resolves an EmailServicesFunction', async () => { + const cs = await ComponentSetBuilder.build({ + sourcepath: [path.join(session.project.dir, 'force-app', 'main', 'default', 'emailservices')], + }); + expect(cs['components'].size).to.equal(1); + expect((await cs.getObject()).Package.types[0].name).to.equal('EmailServicesFunction'); + }); + it('it ignores package manifest files', async () => { const cs = await ComponentSetBuilder.build({ sourcepath: [path.join(session.project.dir, 'package-manifest')] }); expect(cs['components'].size).to.equal(0); diff --git a/test/nuts/suggestType/testProj/force-app/main/default/classes/DummyClass.clss b/test/nuts/suggestType/testProj/force-app/main/default/classes/DummyClass.clss new file mode 100644 index 0000000000..4e198f5059 --- /dev/null +++ b/test/nuts/suggestType/testProj/force-app/main/default/classes/DummyClass.clss @@ -0,0 +1,4 @@ +global class DummyClass { + result.success = true; + return result; +} diff --git a/test/nuts/suggestType/testProj/force-app/main/default/classes/MyEmailHandler.cls b/test/nuts/suggestType/testProj/force-app/main/default/classes/MyEmailHandler.cls new file mode 100644 index 0000000000..e5bf4b950b --- /dev/null +++ b/test/nuts/suggestType/testProj/force-app/main/default/classes/MyEmailHandler.cls @@ -0,0 +1,10 @@ +global class MyEmailHandler implements Messaging.InboundEmailHandler { + global Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email, Messaging.InboundEnvelope envelope) { + Messaging.InboundEmailResult result = new Messaging.InboundEmailresult(); + + // logic here + + result.success = true; + return result; + } +} diff --git a/test/nuts/suggestType/testProj/force-app/main/default/classes/MyEmailHandler.cls-meta.xml b/test/nuts/suggestType/testProj/force-app/main/default/classes/MyEmailHandler.cls-meta.xml new file mode 100644 index 0000000000..c8d39860d4 --- /dev/null +++ b/test/nuts/suggestType/testProj/force-app/main/default/classes/MyEmailHandler.cls-meta.xml @@ -0,0 +1,6 @@ + + + 57.0 + Active + MyEmailHandler.clz + diff --git a/test/nuts/suggestType/testProj/force-app/main/default/emailservices/EmailServiceName.xml-meta.xml b/test/nuts/suggestType/testProj/force-app/main/default/emailservices/EmailServiceName.xml-meta.xml new file mode 100644 index 0000000000..c7db5c3a0e --- /dev/null +++ b/test/nuts/suggestType/testProj/force-app/main/default/emailservices/EmailServiceName.xml-meta.xml @@ -0,0 +1,22 @@ + + + MyEmailHandler + None + Discard + Discard + + alm-cli@salesforce.com + MyEmailAddress + true + emailservicename + test-x4oy7orqzey1@example.com + + Discard + EmailServiceName + false + false + false + false + false + Discard + From eddb7a210099c5fd080850ae85b406e11bb5aab1 Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Tue, 2 May 2023 13:02:58 -0500 Subject: [PATCH 05/11] chore: bump core --- package.json | 2 +- yarn.lock | 17 ++++------------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index aa7621934f..ab697d2468 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "node": ">=14.0.0" }, "dependencies": { - "@salesforce/core": "^3.34.9", + "@salesforce/core": "^3.36.0", "@salesforce/kit": "^1.9.2", "@salesforce/ts-types": "^1.7.2", "archiver": "^5.3.1", diff --git a/yarn.lock b/yarn.lock index 4596f66416..54852243d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -890,16 +890,15 @@ strip-ansi "6.0.1" ts-retry-promise "^0.7.0" -"@salesforce/core@^3.34.6", "@salesforce/core@^3.34.9": - version "3.34.9" - resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-3.34.9.tgz#3176fa71c32b2efe448840ed597c6f138557400e" - integrity sha512-+1U2ISyyQQwLR2a/0PeXB3ljm0Jw2Qk197EVIj48DPYav0rmidzb8pf45ZTRXkSVQkxUdzHp/X3w4FQFkDAbWg== +"@salesforce/core@^3.34.6", "@salesforce/core@^3.36.0": + version "3.36.0" + resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-3.36.0.tgz#cbff147d888eee0b921e368c1fdc1a1a3c2eacab" + integrity sha512-LOeSJgozf5B1S8C98/K3afewRsathfEm+HrXxaFXJjITFfIhxqcIDB5xC1cw0VikfRUlq7dQocmVsvezcK0Ghw== dependencies: "@salesforce/bunyan" "^2.0.0" "@salesforce/kit" "^1.9.2" "@salesforce/schemas" "^1.5.1" "@salesforce/ts-types" "^1.7.2" - "@types/graceful-fs" "^4.1.6" "@types/semver" "^7.3.13" ajv "^8.12.0" archiver "^5.3.0" @@ -907,7 +906,6 @@ debug "^3.2.7" faye "^1.4.0" form-data "^4.0.0" - graceful-fs "^4.2.11" js2xmlparser "^4.0.1" jsforce "^2.0.0-beta.21" jsonwebtoken "9.0.0" @@ -1139,13 +1137,6 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/graceful-fs@^4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" - integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== - dependencies: - "@types/node" "*" - "@types/http-cache-semantics@*": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" From 34b9c01ebc74f0f836a35e2c6b7222c572f19068 Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Tue, 2 May 2023 13:06:09 -0500 Subject: [PATCH 06/11] chore: graceful-fs types --- package.json | 1 + yarn.lock | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/package.json b/package.json index ab697d2468..a65d8b5728 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@types/archiver": "^5.3.1", "@types/deep-equal-in-any-order": "^1.0.1", "@types/fast-levenshtein": "^0.0.2", + "@types/graceful-fs": "^4.1.6", "@types/mime": "2.0.3", "@types/minimatch": "^5.1.2", "@types/proxy-from-env": "^1.0.1", diff --git a/yarn.lock b/yarn.lock index 54852243d5..8043147097 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1137,6 +1137,13 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/graceful-fs@^4.1.6": + version "4.1.6" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" + integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== + dependencies: + "@types/node" "*" + "@types/http-cache-semantics@*": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" From c09415fce2898fcf57bfda241aa53a47b7052074 Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Thu, 4 May 2023 10:50:15 -0500 Subject: [PATCH 07/11] fix: better manifest detection --- src/resolve/metadataResolver.ts | 13 +++++++++---- test/nuts/suggestType/suggestType.nut.ts | 19 ++++++++++++++++++- .../default/labels/CustomLabels.labels.xml | 10 ++++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 test/nuts/suggestType/testProj/force-app/main/default/labels/CustomLabels.labels.xml diff --git a/src/resolve/metadataResolver.ts b/src/resolve/metadataResolver.ts index c3a5b13ec2..17dc3c8c18 100644 --- a/src/resolve/metadataResolver.ts +++ b/src/resolve/metadataResolver.ts @@ -6,6 +6,7 @@ */ import { basename, dirname, join, sep } from 'path'; import { Lifecycle, Messages, SfError, Logger } from '@salesforce/core'; +import { readFileSync } from 'graceful-fs'; import { extName, parentName, parseMetadataXml } from '../utils'; import { MetadataType, RegistryAccess } from '../registry'; import { ComponentSet } from '../collections'; @@ -142,10 +143,14 @@ export class MetadataResolver { return shouldResolve ? adapter.getComponent(fsPath, isResolvingSource) : undefined; } - // If a file ends with .xml and is not a metadata type, it is likely a package manifest - // In the past, these were "resolved" as EmailServicesFunction. See note on "attempt 3" in resolveType() below. - if (fsPath.endsWith('.xml') && !fsPath.endsWith(META_XML_SUFFIX)) { - this.logger.debug(`Could not resolve type for ${fsPath}. It is likely a package manifest. Moving on.`); + // Read the file and check to see if it contains the string " { let session: TestSession; @@ -119,6 +121,21 @@ describe('suggest types', () => { expect((await cs.getObject()).Package.types[0].name).to.equal('EmailServicesFunction'); }); + // This will likely not provide great suggestions, but at least the user is alerted to file causing the issue + it('it errors on very incorrectly named metadata', async () => { + try { + await ComponentSetBuilder.build({ + sourcepath: [path.join(session.project.dir, 'force-app', 'main', 'default', 'labels')], + }); + throw new Error('This test should have thrown'); + } catch (err) { + const error = err as SfError; + expect(error.name).to.equal('TypeInferenceError'); + expect(error.actions).to.include('A search for the ".xml" filename suffix found the following close matches:'); + expect(error.actions).to.include('-- Did you mean ".xml" instead for the "EmailServicesFunction" metadata type?'); + } + }); + it('it ignores package manifest files', async () => { const cs = await ComponentSetBuilder.build({ sourcepath: [path.join(session.project.dir, 'package-manifest')] }); expect(cs['components'].size).to.equal(0); diff --git a/test/nuts/suggestType/testProj/force-app/main/default/labels/CustomLabels.labels.xml b/test/nuts/suggestType/testProj/force-app/main/default/labels/CustomLabels.labels.xml new file mode 100644 index 0000000000..552fe7303c --- /dev/null +++ b/test/nuts/suggestType/testProj/force-app/main/default/labels/CustomLabels.labels.xml @@ -0,0 +1,10 @@ + + + + MyCustomLabel + en_US + true + MyCustomLabel + foo + + From 3e0bb5dd4249ffe6f3ce28425a2c8441124e7c0b Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Fri, 5 May 2023 14:58:12 -0500 Subject: [PATCH 08/11] chore: better manifest detection --- src/client/metadataTransfer.ts | 6 ++-- src/resolve/metadataResolver.ts | 28 ++++++++++++------- test/client/metadataApiDeploy.test.ts | 4 +-- test/client/metadataApiRetrieve.test.ts | 4 +-- test/nuts/suggestType/suggestType.nut.ts | 7 ++++- .../package-non-default.xml | 8 ++++++ 6 files changed, 39 insertions(+), 18 deletions(-) create mode 100644 test/nuts/suggestType/testProj/package-manifest-2/package-non-default.xml diff --git a/src/client/metadataTransfer.ts b/src/client/metadataTransfer.ts index 2ca474dbaa..842811f9f3 100644 --- a/src/client/metadataTransfer.ts +++ b/src/client/metadataTransfer.ts @@ -162,13 +162,13 @@ export abstract class MetadataTransfer< } protected async maybeSaveTempDirectory(target: SfdxFileFormat, cs?: ComponentSet): Promise { - const mdapiTempDir = process.env.SFDX_MDAPI_TEMP_DIR; + const mdapiTempDir = process.env.SF_MDAPI_TEMP_DIR; if (mdapiTempDir) { await Lifecycle.getInstance().emitWarning( - 'The SFDX_MDAPI_TEMP_DIR environment variable is set, which may degrade performance' + 'The SF_MDAPI_TEMP_DIR environment variable is set, which may degrade performance' ); this.logger.debug( - `Converting metadata to: ${mdapiTempDir} because the SFDX_MDAPI_TEMP_DIR environment variable is set` + `Converting metadata to: ${mdapiTempDir} because the SF_MDAPI_TEMP_DIR environment variable is set` ); try { const source = cs ?? this.components ?? new ComponentSet(); diff --git a/src/resolve/metadataResolver.ts b/src/resolve/metadataResolver.ts index 17dc3c8c18..f941842780 100644 --- a/src/resolve/metadataResolver.ts +++ b/src/resolve/metadataResolver.ts @@ -6,7 +6,6 @@ */ import { basename, dirname, join, sep } from 'path'; import { Lifecycle, Messages, SfError, Logger } from '@salesforce/core'; -import { readFileSync } from 'graceful-fs'; import { extName, parentName, parseMetadataXml } from '../utils'; import { MetadataType, RegistryAccess } from '../registry'; import { ComponentSet } from '../collections'; @@ -143,15 +142,24 @@ export class MetadataResolver { return shouldResolve ? adapter.getComponent(fsPath, isResolvingSource) : undefined; } - // Read the file and check to see if it contains the string " { it('should save the temp directory if the environment variable is set', async () => { try { - process.env.SFDX_MDAPI_TEMP_DIR = 'test'; + process.env.SF_MDAPI_TEMP_DIR = 'test'; const components = new ComponentSet([matchingContentFile.COMPONENT]); const { operation, convertStub, deployStub } = await stubMetadataDeploy($$, testOrg, { components, @@ -95,7 +95,7 @@ describe('MetadataApiDeploy', () => { expect(deployStub.firstCall.args[0]).to.equal(zipBuffer); expect(getString(convertStub.secondCall.args[2], 'outputDirectory', '')).to.equal('test'); } finally { - delete process.env.SFDX_MDAPI_TEMP_DIR; + delete process.env.SF_MDAPI_TEMP_DIR; } }); diff --git a/test/client/metadataApiRetrieve.test.ts b/test/client/metadataApiRetrieve.test.ts index 6ea404bce6..4d544a9a03 100644 --- a/test/client/metadataApiRetrieve.test.ts +++ b/test/client/metadataApiRetrieve.test.ts @@ -303,7 +303,7 @@ describe('MetadataApiRetrieve', () => { it('should save the temp directory if the environment variable is set', async () => { try { - process.env.SFDX_MDAPI_TEMP_DIR = 'test'; + process.env.SF_MDAPI_TEMP_DIR = 'test'; const toRetrieve = new ComponentSet([COMPONENT]); const { operation, convertStub } = await stubMetadataRetrieve($$, testOrg, { toRetrieve, @@ -317,7 +317,7 @@ describe('MetadataApiRetrieve', () => { expect(getString(convertStub.secondCall.args[2], 'outputDirectory', '')).to.equal('test'); } finally { - delete process.env.SFDX_MDAPI_TEMP_DIR; + delete process.env.SF_MDAPI_TEMP_DIR; } }); diff --git a/test/nuts/suggestType/suggestType.nut.ts b/test/nuts/suggestType/suggestType.nut.ts index 4e831e63e8..2c2034842b 100644 --- a/test/nuts/suggestType/suggestType.nut.ts +++ b/test/nuts/suggestType/suggestType.nut.ts @@ -136,8 +136,13 @@ describe('suggest types', () => { } }); - it('it ignores package manifest files', async () => { + it('it ignores package manifest files with default name', async () => { const cs = await ComponentSetBuilder.build({ sourcepath: [path.join(session.project.dir, 'package-manifest')] }); expect(cs['components'].size).to.equal(0); }); + + it('it ignores package manifest files with non-default name', async () => { + const cs = await ComponentSetBuilder.build({ sourcepath: [path.join(session.project.dir, 'package-manifest-2')] }); + expect(cs['components'].size).to.equal(0); + }); }); diff --git a/test/nuts/suggestType/testProj/package-manifest-2/package-non-default.xml b/test/nuts/suggestType/testProj/package-manifest-2/package-non-default.xml new file mode 100644 index 0000000000..e4f345f282 --- /dev/null +++ b/test/nuts/suggestType/testProj/package-manifest-2/package-non-default.xml @@ -0,0 +1,8 @@ + + + + label_one + CustomLabel + + 55.0 + \ No newline at end of file From 6c5d1b91191f95e8a09c6e083d7b2c5edaa40291 Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Mon, 8 May 2023 14:52:57 -0500 Subject: [PATCH 09/11] fix: better meta suffix guesses --- messages/sdr.md | 2 +- src/resolve/metadataResolver.ts | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/messages/sdr.md b/messages/sdr.md index e19df84828..cd5f752d18 100644 --- a/messages/sdr.md +++ b/messages/sdr.md @@ -160,7 +160,7 @@ The uniqueIdElement %s was not found the child (reading %s at %s). # suggest_type_header -A search for the %s suffix found the following close matches: +A metadata type lookup for "%s" found the following close matches: # suggest_type_did_you_mean diff --git a/src/resolve/metadataResolver.ts b/src/resolve/metadataResolver.ts index f941842780..569f04bb98 100644 --- a/src/resolve/metadataResolver.ts +++ b/src/resolve/metadataResolver.ts @@ -262,22 +262,28 @@ export class MetadataResolver { private getSuggestionsForUnresolvedTypes(fsPath: string): string[] { const parsedMetaXml = parseMetadataXml(fsPath); const metaSuffix = parsedMetaXml?.suffix; + // Finds close matches for meta suffixes + // Examples: https://regex101.com/r/vbRjwy/1 + const closeMetaSuffix = new RegExp(/.+\.([^.-]+)(?:-.*)?\.xml/).exec(basename(fsPath)); - // Analogous to "attempt 2" and "attempt 3" above - const guesses = metaSuffix - ? this.registry.guessTypeBySuffix(metaSuffix) - : this.registry.guessTypeBySuffix(extName(fsPath)); + let guesses; + + if (metaSuffix) { + guesses = this.registry.guessTypeBySuffix(metaSuffix) + } else if (!metaSuffix && closeMetaSuffix) { + guesses = this.registry.guessTypeBySuffix(closeMetaSuffix[1]); + } else { + this.registry.guessTypeBySuffix(extName(fsPath)); + } // If guesses were found, format an array of strings to be passed to SfError's actions return guesses && guesses.length > 0 ? [ - messages.getMessage('suggest_type_header', [ - metaSuffix ? `".${parsedMetaXml.suffix}-meta.xml" metadata` : `".${extName(fsPath)}" filename`, - ]), + messages.getMessage('suggest_type_header', [ basename(fsPath) ]), ...guesses.map((guess) => messages.getMessage('suggest_type_did_you_mean', [ guess.suffixGuess, - metaSuffix ? '-meta.xml' : '', + (metaSuffix || closeMetaSuffix) ? '-meta.xml' : '', guess.metadataTypeGuess.name, ]) ), From 7eaa54754c97e7f421d83bd75464bab6d94d0d7c Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Mon, 8 May 2023 14:56:27 -0500 Subject: [PATCH 10/11] fix: filename lookup --- src/resolve/metadataResolver.ts | 2 +- test/nuts/suggestType/suggestType.nut.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resolve/metadataResolver.ts b/src/resolve/metadataResolver.ts index 569f04bb98..79c7db3ecd 100644 --- a/src/resolve/metadataResolver.ts +++ b/src/resolve/metadataResolver.ts @@ -273,7 +273,7 @@ export class MetadataResolver { } else if (!metaSuffix && closeMetaSuffix) { guesses = this.registry.guessTypeBySuffix(closeMetaSuffix[1]); } else { - this.registry.guessTypeBySuffix(extName(fsPath)); + guesses = this.registry.guessTypeBySuffix(extName(fsPath)); } // If guesses were found, format an array of strings to be passed to SfError's actions diff --git a/test/nuts/suggestType/suggestType.nut.ts b/test/nuts/suggestType/suggestType.nut.ts index 2c2034842b..e6f6647296 100644 --- a/test/nuts/suggestType/suggestType.nut.ts +++ b/test/nuts/suggestType/suggestType.nut.ts @@ -42,7 +42,7 @@ describe('suggest types', () => { const error = err as SfError; expect(error.name).to.equal('TypeInferenceError'); expect(error.actions).to.include( - 'A search for the ".objct-meta.xml" metadata suffix found the following close matches:' + 'A metadata type lookup for "MyTestObject__c.objct-meta.xml" found the following close matches:' ); expect(error.actions).to.include( '-- Did you mean ".object-meta.xml" instead for the "CustomObject" metadata type?' From 95849e4555130c41f9405a43381f472a0bf692ca Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Mon, 8 May 2023 15:05:18 -0500 Subject: [PATCH 11/11] fix: guess nuts --- test/nuts/suggestType/suggestType.nut.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/nuts/suggestType/suggestType.nut.ts b/test/nuts/suggestType/suggestType.nut.ts index e6f6647296..a941764e26 100644 --- a/test/nuts/suggestType/suggestType.nut.ts +++ b/test/nuts/suggestType/suggestType.nut.ts @@ -59,7 +59,7 @@ describe('suggest types', () => { } catch (err) { const error = err as SfError; expect(error.name).to.equal('TypeInferenceError'); - expect(error.actions).to.include('A search for the ".clss" filename suffix found the following close matches:'); + expect(error.actions).to.include('A metadata type lookup for "DummyClass.clss" found the following close matches:'); expect(error.actions).to.include('-- Did you mean ".cls" instead for the "ApexClass" metadata type?'); } }); @@ -74,7 +74,7 @@ describe('suggest types', () => { const error = err as SfError; expect(error.name).to.equal('TypeInferenceError'); expect(error.actions).to.include( - 'A search for the ".Layout-meta.xml" metadata suffix found the following close matches:' + 'A metadata type lookup for "MyTestObject__c-MyTestObject Layout.Layout-meta.xml" found the following close matches:' ); expect(error.actions).to.include('-- Did you mean ".layout-meta.xml" instead for the "Layout" metadata type?'); } @@ -90,7 +90,7 @@ describe('suggest types', () => { const error = err as SfError; expect(error.name).to.equal('TypeInferenceError'); expect(error.actions).to.include( - 'A search for the ".tabsss-meta.xml" metadata suffix found the following close matches:' + 'A metadata type lookup for "Settings.tabsss-meta.xml" found the following close matches:' ); expect(error.actions).to.include( '-- Did you mean ".labels-meta.xml" instead for the "CustomLabels" metadata type?' @@ -121,7 +121,7 @@ describe('suggest types', () => { expect((await cs.getObject()).Package.types[0].name).to.equal('EmailServicesFunction'); }); - // This will likely not provide great suggestions, but at least the user is alerted to file causing the issue + // This uses the closeMetaSuffix lookup it('it errors on very incorrectly named metadata', async () => { try { await ComponentSetBuilder.build({ @@ -131,8 +131,8 @@ describe('suggest types', () => { } catch (err) { const error = err as SfError; expect(error.name).to.equal('TypeInferenceError'); - expect(error.actions).to.include('A search for the ".xml" filename suffix found the following close matches:'); - expect(error.actions).to.include('-- Did you mean ".xml" instead for the "EmailServicesFunction" metadata type?'); + expect(error.actions).to.include('A metadata type lookup for "CustomLabels.labels.xml" found the following close matches:'); + expect(error.actions).to.include('-- Did you mean ".labels-meta.xml" instead for the "CustomLabels" metadata type?'); } });