diff --git a/src/bids/index.js b/src/bids/index.js index 992df04b..c6548af1 100644 --- a/src/bids/index.js +++ b/src/bids/index.js @@ -1,7 +1,7 @@ import { buildBidsSchemas } from './schema' import { BidsJsonFile, BidsSidecar } from './types/json' import { BidsTsvFile } from './types/tsv' -import { BidsHedIssue, BidsIssue } from './types/issues' +import { BidsHedIssue } from './types/issues' import BidsHedSidecarValidator from './validator/sidecarValidator' import BidsHedTsvValidator from './validator/tsvValidator' @@ -9,7 +9,6 @@ export { BidsTsvFile, BidsJsonFile, BidsSidecar, - BidsIssue, BidsHedIssue, BidsHedSidecarValidator, BidsHedTsvValidator, @@ -20,7 +19,6 @@ export default { BidsTsvFile, BidsJsonFile, BidsSidecar, - BidsIssue, BidsHedIssue, BidsHedSidecarValidator, BidsHedTsvValidator, diff --git a/src/bids/tsvParser.js b/src/bids/tsvParser.js index 46557b6e..9e9e5c6a 100644 --- a/src/bids/tsvParser.js +++ b/src/bids/tsvParser.js @@ -13,7 +13,7 @@ const isContentfulRow = (row) => row && !/^\s*$/.test(row) * Parse a TSV file. * * @param {string} contents The contents of a TSV file. - * @returns {Map} The parsed contents of the TSV file. + * @returns {Map} The parsed contents of the TSV file. */ export function parseTSV(contents) { const columns = new Map() @@ -40,7 +40,7 @@ export function parseTSV(contents) { * Convert parsed TSV file data from the old BIDS format to the new BIDS format. * * @param {{headers: string[], rows: string[][]}} oldParsedTsv Parsed TSV data using the old format - * @returns {Map} The parsed contents of the TSV file, using the new format. + * @returns {Map} The parsed contents of the TSV file, using the new format. */ export function convertParsedTSVData(oldParsedTsv) { const columns = new Map() diff --git a/src/bids/types/file.js b/src/bids/types/file.js index 78788e11..af46fe07 100644 --- a/src/bids/types/file.js +++ b/src/bids/types/file.js @@ -13,7 +13,7 @@ export class BidsFile { /** * The Object representing this file data. - * This is used to generate {@link BidsIssue} objects. + * This is used to generate {@link BidsHedIssue} objects. * @type {Object} */ @@ -40,7 +40,7 @@ export class BidsFile { * Validate this validator's tsv file. * * @param {Schemas} schemas - The HED schemas used to validate this file. - * @returns {BidsIssue[]} - Any issues found during validation of this TSV file. + * @returns {BidsHedIssue[]} - Any issues found during validation of this TSV file. */ validate(schemas) { if (!this.hasHedData) { @@ -63,9 +63,19 @@ export class BidsFile { } } + /** + * Determine whether this file has any HED data. + * + * @returns {boolean} + */ + get hasHedData() { + return false + } + /** * The validator class used to validate this file. - * @returns {BidsValidator} + * + * @returns {function} (typeof BidsValidator) A subclass constructor of {@link BidsValidator}. */ get validatorClass() { return this._validatorClass diff --git a/src/bids/types/issues.js b/src/bids/types/issues.js index 36173918..a398b511 100644 --- a/src/bids/types/issues.js +++ b/src/bids/types/issues.js @@ -1,13 +1,13 @@ import { generateIssue, IssueError } from '../../issues/issues' -const bidsHedErrorCodes = new Set([104, 106, 107]) +const bidsHedErrorCodes = new Set(['HED_ERROR', 'HED_INTERNAL_ERROR']) -export class BidsIssue { +export class BidsHedIssue { /** * The BIDS issue code. - * @type {number} + * @type {string} */ - code + bidsCode /** * The file associated with this issue. * @type {Object} @@ -17,12 +17,33 @@ export class BidsIssue { * The evidence for this issue. * @type {string} */ - evidence + message + /** + * The HED Issue object corresponding to this object. + * @type {Issue} + */ + hedIssue - constructor(issueCode, file, evidence) { - this.code = issueCode + /** + * Constructor. + * + * @param {Issue} hedIssue The HED issue object to be wrapped. + * @param {Object} file The file this error occurred in. + */ + constructor(hedIssue, file) { + this.hedIssue = hedIssue + this.bidsCode = BidsHedIssue._determineBidsIssueCode(hedIssue) + this.message = hedIssue.message this.file = file - this.evidence = evidence + } + + /** + * The HED spec code for this issue. + * + * @returns {string} + */ + get hedCode() { + return this.hedIssue.hedCode } /** @@ -30,59 +51,46 @@ export class BidsIssue { * * @returns {boolean} */ - isError() { - return bidsHedErrorCodes.has(this.code) + get isError() { + return bidsHedErrorCodes.has(this.bidsCode) } /** * Determine if any of the passed issues are errors. * - * @param {BidsIssue[]} issues A list of issues. + * @param {BidsHedIssue[]} issues A list of issues. * @returns {boolean} Whether any of the passed issues are errors (rather than warnings). */ static anyAreErrors(issues) { - return issues.some((issue) => issue.isError()) + return issues.some((issue) => issue.isError) } - static async generateInternalErrorPromise(error, errorFile) { - return [new BidsHedIssue(generateIssue('internalError', { message: error.message }), errorFile)] - } -} - -export class BidsHedIssue extends BidsIssue { /** - * The HED Issue object corresponding to this object. - * @type {Issue} - */ - hedIssue - - /** - * Constructor. + * Generate a {@link Promise} with an internal error. * - * @param {Issue} hedIssue The HED issue object to be wrapped. - * @param {Object} file The file this error occurred in. + * @param {string} error The error message. + * @param {Object} errorFile The file this error occurred in. + * @return {Promise} A promise resolving to a singleton array containing an internal error {@link BidsHedIssue}. */ - constructor(hedIssue, file) { - super(BidsHedIssue._determineBidsIssueCode(hedIssue), file, hedIssue.message) - - this.hedIssue = hedIssue + static async generateInternalErrorPromise(error, errorFile) { + return [new BidsHedIssue(generateIssue('internalError', { message: error.message }), errorFile)] } /** * Determine the BIDS issue code for this issue. * * @param {Issue} hedIssue The HED issue object to be wrapped. - * @returns {number} The BIDS issue code for this issue. + * @returns {string} The BIDS issue code for this issue. * @private */ static _determineBidsIssueCode(hedIssue) { - if (hedIssue.internalCode === 'internalError' || hedIssue.internalCode === 'internalConsistencyError') { - return 106 + if (hedIssue.internalCode === 'internalError') { + return 'HED_INTERNAL_ERROR' } if (hedIssue.level === 'warning') { - return 105 + return 'HED_WARNING' } - return 104 + return 'HED_ERROR' } /** diff --git a/src/bids/types/json.js b/src/bids/types/json.js index 37e8a62a..7e82d73c 100644 --- a/src/bids/types/json.js +++ b/src/bids/types/json.js @@ -104,9 +104,9 @@ export class BidsSidecar extends BidsJsonFile { } else if (!defManager) { this.definitions = new DefinitionManager() } else { - IssueError.generateAndThrow('internalError', { - message: 'Improper format for defManager parameter -- must be null or DefinitionManager', - }) + IssueError.generateAndThrowInternalError( + 'Improper format for defManager parameter -- must be null or DefinitionManager', + ) } } @@ -222,9 +222,7 @@ export class BidsSidecar extends BidsJsonFile { } else if (hedData instanceof Map) { this._parseCategorySplice(sidecarKey, hedData) } else if (hedData) { - IssueError.generateAndThrow('internalConsistencyError', { - message: 'Unexpected type found in bidsFile parsedHedData map.', - }) + IssueError.generateAndThrowInternalError('Unexpected type found in bidsFile parsedHedData map.') } } } @@ -250,9 +248,9 @@ export class BidsSidecar extends BidsJsonFile { /** * Add a list of columnSplices to a key map. - * @param {Set} keyReferences + * @param {Set|null} keyReferences * @param {ParsedHedColumnSplice[]} columnSplices - * @returns {*|Set} + * @returns {Set} * @private */ _processColumnSplices(keyReferences, columnSplices) { @@ -341,13 +339,14 @@ export class BidsSidecarKey { } /** - * Parse the value string in a bidsFile - * @param {Schemas} hedSchemas - The HED schemas to use. - * @returns {Issue[]} - * @private + * Parse the value string in a bidsFile. * * ### Note: * The value strings cannot contain definitions. + * + * @param {Schemas} hedSchemas - The HED schemas to use. + * @returns {Issue[]} + * @private */ _parseValueString(hedSchemas) { const [parsedString, parsingIssues] = parseHedString(this.valueString, hedSchemas, false, true) diff --git a/src/bids/types/tsv.js b/src/bids/types/tsv.js index 2a8ce3aa..614bee05 100644 --- a/src/bids/types/tsv.js +++ b/src/bids/types/tsv.js @@ -12,7 +12,7 @@ import { IssueError } from '../../issues/issues' export class BidsTsvFile extends BidsFile { /** * This file's parsed TSV data. - * @type {Map} + * @type {Map} */ parsedTsv /** @@ -46,7 +46,7 @@ export class BidsTsvFile extends BidsFile { } else if (isPlainObject(tsvData)) { this.parsedTsv = convertParsedTSVData(tsvData) } else { - IssueError.generateAndThrow('internalError', { message: 'parsedTsv has an invalid type' }) + IssueError.generateAndThrowInternalError('parsedTsv has an invalid type') } this.mergedSidecar = new BidsSidecar(name, this.file, mergedDictionary, defManager) @@ -140,7 +140,7 @@ export class BidsTsvElement { /** * Create a string list of a list of BidsTsvElement objects. - * @param BidsTsvElement[] elements - A list of elements to construct line numbers from. + * @param {BidsTsvElement[]} elements - A list of elements to construct line numbers from. * @returns {string} - A string with the list of line numbers for error messages. */ static getTsvLines(elements) { diff --git a/src/bids/validator/sidecarValidator.js b/src/bids/validator/sidecarValidator.js index 097249d3..8f594553 100644 --- a/src/bids/validator/sidecarValidator.js +++ b/src/bids/validator/sidecarValidator.js @@ -21,7 +21,7 @@ export class BidsHedSidecarValidator extends BidsValidator { /** * Validate a BIDS JSON bidsFile file. This method returns the complete issue list for convenience. * - * @returns {BidsIssue[]} - Any issues found during validation of this bidsFile file. + * @returns {BidsHedIssue[]} - Any issues found during validation of this bidsFile file. */ validate() { // Allow schema to be set a validation time -- this is checked by the superclass of BIDS file @@ -40,7 +40,7 @@ export class BidsHedSidecarValidator extends BidsValidator { /** * Validate this bidsFile's HED strings. * - * @returns {BidsIssue[]} All issues found. + * @returns {BidsHedIssue[]} All issues found. */ _validateStrings() { const issues = [] @@ -55,9 +55,7 @@ export class BidsHedSidecarValidator extends BidsValidator { issues.push(...this._checkDetails(sidecarKeyName, valueString)) } } else { - IssueError.generateAndThrow('internalConsistencyError', { - message: 'Unexpected type found in bidsFile parsedHedData map.', - }) + IssueError.generateAndThrowInternalError('Unexpected type found in bidsFile parsedHedData map.') } } return issues @@ -127,7 +125,7 @@ export class BidsHedSidecarValidator extends BidsValidator { /** * Validate this bidsFile's curly braces -- checking recursion and missing columns. * - * @returns {BidsIssue[]} All issues found. + * @returns {BidsHedIssue[]} All issues found. */ _validateCurlyBraces() { const issues = [] diff --git a/src/bids/validator/tsvValidator.js b/src/bids/validator/tsvValidator.js index b9b5e2c7..91eac32a 100644 --- a/src/bids/validator/tsvValidator.js +++ b/src/bids/validator/tsvValidator.js @@ -1,4 +1,4 @@ -import { BidsHedIssue, BidsIssue } from '../types/issues' +import { BidsHedIssue } from '../types/issues' import { BidsTsvElement, BidsTsvRow } from '../types/tsv' import { BidsValidator } from './validator' import { parseHedString } from '../../parser/parser' @@ -32,14 +32,14 @@ export class BidsHedTsvValidator extends BidsValidator { /** * Validate a BIDS TSV file. This method returns the complete issue list for convenience. * - * @returns {BidsIssue[]} - Any issues found during validation of this TSV file. + * @returns {BidsHedIssue[]} - Any issues found during validation of this TSV file. */ validate() { // Validate the BIDS bidsFile if it exists. if (this.bidsFile.mergedSidecar) { const sidecarIssues = this.bidsFile.mergedSidecar.validate(this.hedSchemas) this.issues.push(...sidecarIssues) - if (BidsIssue.anyAreErrors(sidecarIssues)) { + if (BidsHedIssue.anyAreErrors(sidecarIssues)) { return this.issues } } @@ -47,14 +47,14 @@ export class BidsHedTsvValidator extends BidsValidator { // Valid the HED column by itself. const hedColumnIssues = this._validateHedColumn() this.issues.push(...hedColumnIssues) - if (BidsIssue.anyAreErrors(this.issues)) { + if (BidsHedIssue.anyAreErrors(this.issues)) { return this.issues } // Now do a full validation const bidsHedTsvParser = new BidsHedTsvParser(this.bidsFile, this.hedSchemas) const [bidsEvents, parsingIssues] = bidsHedTsvParser.parse() this.issues.push(...parsingIssues) - if (!BidsIssue.anyAreErrors(this.issues)) { + if (!BidsHedIssue.anyAreErrors(this.issues)) { this.issues.push(...this.validateDataset(bidsEvents)) } return this.issues @@ -63,7 +63,7 @@ export class BidsHedTsvValidator extends BidsValidator { /** * Validate this TSV file's HED column. * - * @returns {BidsIssue[]} - Issues found in validating the HED column without sidecar information. + * @returns {BidsHedIssue[]} - Issues found in validating the HED column without sidecar information. * @private */ _validateHedColumn() { @@ -80,7 +80,7 @@ export class BidsHedTsvValidator extends BidsValidator { * * @param {string} hedString - The string to be validated. * @param {number} rowIndex - The index of this row in the TSV file. - * @returns {BidsIssue[]} - Specific issues found in validating the HED column + * @returns {BidsHedIssue[]} - Specific issues found in validating the HED column * @private */ _validateHedColumnString(hedString, rowIndex) { @@ -165,13 +165,13 @@ export class BidsHedTsvValidator extends BidsValidator { /** * Check for duplicate tags when multiple rows with the same onset. * - * @param {BidsTsvElement[]} elements - The elements representing the tsv file. - * @returns {BidsHedIssue[]} - Errors in temporal relationships among events. - * @private - * * ### Note: * Duplicate onsets are relatively rare and duplicates for single rows are checked when a ParsedHedString is * constructed. + * + * @param {BidsTsvElement[]} elements - The elements representing the tsv file. + * @returns {BidsHedIssue[]} - Errors in temporal relationships among events. + * @private */ _checkDuplicatesAcrossRows(elements) { const duplicateMap = this._getOnsetMap(elements) diff --git a/src/bids/validator/validator.js b/src/bids/validator/validator.js index fcd77332..18ac2713 100644 --- a/src/bids/validator/validator.js +++ b/src/bids/validator/validator.js @@ -14,7 +14,7 @@ export class BidsValidator { hedSchemas /** * The issues found during validation. - * @type {BidsIssue[]} + * @type {BidsHedIssue[]} */ issues @@ -33,7 +33,7 @@ export class BidsValidator { /** * Validate a BIDS TSV file. This method returns the complete issue list for convenience. * - * @returns {BidsIssue[]} - Any issues found during validation of this TSV file. + * @returns {BidsHedIssue[]} - Any issues found during validation of this TSV file. */ validate() { return this.issues diff --git a/src/issues/data.js b/src/issues/data.js index 4d7f19d7..9dce777d 100644 --- a/src/issues/data.js +++ b/src/issues/data.js @@ -475,13 +475,8 @@ export default { message: stringTemplate`Unknown HED error "${'internalCode'}" - parameters: "${'parameters'}".`, }, internalError: { - hedCode: 'GENERIC_ERROR', + hedCode: 'INTERNAL_ERROR', level: 'error', message: stringTemplate`Internal error - message: "${'message'}".`, }, - internalConsistencyError: { - hedCode: 'GENERIC_ERROR', - level: 'error', - message: stringTemplate`Internal consistency error - message: "${'message'}".`, - }, } diff --git a/src/issues/issues.js b/src/issues/issues.js index 0edb9382..36b5c1b0 100644 --- a/src/issues/issues.js +++ b/src/issues/issues.js @@ -9,9 +9,15 @@ export class IssueError extends Error { */ issue + /** + * Constructor. + * + * @param {Issue} issue The associated HED issue. + * @param {...*} params Extra parameters (to be forwarded to the {@link Error} constructor). + */ constructor(issue, ...params) { // Pass remaining arguments (including vendor specific ones) to parent constructor - super(...params) + super(issue.message, ...params) // Maintains proper stack trace for where our error was thrown (only available on V8) if (Error.captureStackTrace) { @@ -20,7 +26,6 @@ export class IssueError extends Error { this.name = 'IssueError' this.issue = issue - this.message = issue.message Object.setPrototypeOf(this, IssueError.prototype) } @@ -35,6 +40,16 @@ export class IssueError extends Error { static generateAndThrow(internalCode, parameters = {}) { throw new IssueError(generateIssue(internalCode, parameters)) } + + /** + * Generate a new {@link Issue} object for an internal error and immediately throw it as an {@link IssueError}. + * + * @param {string} message A message describing the internal error. + * @throws {IssueError} Corresponding to the generated internal error {@link Issue}. + */ + static generateAndThrowInternalError(message = 'Unknown internal error') { + throw new IssueError(generateIssue('internalError', { message })) + } } /** @@ -88,7 +103,7 @@ export class Issue { * * @returns {boolean} */ - isError() { + get isError() { return this.level === 'error' } @@ -137,7 +152,7 @@ export class Issue { * @returns {Array} Returns [boolean, Issue[]] indicate if validation succeeded (i.e. any errors were found)and all issues (both errors and warnings). */ static issueListWithValidStatus(issues) { - return [!issues.some((issue) => issue.isError()), issues] + return [!issues.some((issue) => issue.isError), issues] } } diff --git a/src/parser/reservedChecker.js b/src/parser/reservedChecker.js index 1d8d6fa5..b0a4f635 100644 --- a/src/parser/reservedChecker.js +++ b/src/parser/reservedChecker.js @@ -1,5 +1,5 @@ import reservedTags from '../data/json/reservedTags.json' -import { generateIssue } from '../issues/issues' +import { generateIssue, IssueError } from '../issues/issues' import { getTagListString } from './parseUtils' export class ReservedChecker { @@ -8,7 +8,7 @@ export class ReservedChecker { constructor() { if (ReservedChecker.instance) { - throw new Error('Use ReservedChecker.getInstance() to get an instance of this class.') + IssueError.generateAndThrowInternalError('Use ReservedChecker.getInstance() to get an instance of this class.') } this._initializeReservedTags() diff --git a/src/schema/containers.js b/src/schema/containers.js index a90d6331..d2e4c415 100644 --- a/src/schema/containers.js +++ b/src/schema/containers.js @@ -1,3 +1,4 @@ +import { IssueError } from '../issues/issues' import { getGenerationForSchemaVersion } from '../utils/hedData' /** @@ -122,7 +123,7 @@ export class Schemas { } else if (schemas instanceof Schema) { this.schemas = new Map([['', schemas]]) } else { - throw new Error('Invalid type passed to Schemas constructor') + IssueError.generateAndThrowInternalError('Invalid type passed to Schemas constructor') } if (this.schemas) { this._addNicknamesToSchemas() diff --git a/src/schema/entries.js b/src/schema/entries.js index f94ae80c..22a7f818 100644 --- a/src/schema/entries.js +++ b/src/schema/entries.js @@ -181,7 +181,7 @@ export class SchemaEntryManager extends Memoizer { */ getEntriesWithBooleanAttribute(booleanAttributeName) { return this._memoize(booleanAttributeName, () => { - return this.filter(([_, v]) => { + return this.filter(([, v]) => { return v.hasAttributeName(booleanAttributeName) }) }) @@ -783,7 +783,7 @@ export class SchemaTag extends SchemaEntryWithAttributes { * @type {SchemaTag} * @private */ - _parent + #parent /** * This tag's unit classes. * @type {SchemaUnitClass[]} @@ -803,7 +803,7 @@ export class SchemaTag extends SchemaEntryWithAttributes { * @type {SchemaValueTag} * @private */ - _valueTag + #valueTag /** * Constructor. @@ -858,7 +858,7 @@ export class SchemaTag extends SchemaEntryWithAttributes { * @returns {SchemaValueTag} */ get valueTag() { - return this._valueTag + return this.#valueTag } /** @@ -866,12 +866,8 @@ export class SchemaTag extends SchemaEntryWithAttributes { * @param {SchemaValueTag} newValueTag The new value-taking child tag. */ set valueTag(newValueTag) { - if (this._valueTag === undefined) { - this._valueTag = newValueTag - } else { - IssueError.generateAndThrow('internalError', { - message: `Attempted to set value tag for schema tag "${this.longName}" when it already has one.`, - }) + if (!this.#isPrivateFieldSet(this.#valueTag, 'value tag')) { + this.#valueTag = newValueTag } } @@ -880,7 +876,7 @@ export class SchemaTag extends SchemaEntryWithAttributes { * @type {SchemaTag} */ get parent() { - return this._parent + return this.#parent } /** @@ -888,13 +884,27 @@ export class SchemaTag extends SchemaEntryWithAttributes { * @param {SchemaTag} newParent The new parent tag. */ set parent(newParent) { - if (this._parent === undefined) { - this._parent = newParent - } else { - IssueError.generateAndThrow('internalError', { - message: `Attempted to set parent for schema tag ${this.longName} when it already has one.`, - }) + if (!this.#isPrivateFieldSet(this.#parent, 'parent')) { + this.#parent = newParent + } + } + + /** + * Throw an error if a private field is already set. + * + * @param {*} field The field being set. + * @param {string} fieldName The name of the field (for error reporting). + * @return {boolean} Whether the field is set (never returns true). + * @throws {IssueError} If the field is already set. + * @private + */ + #isPrivateFieldSet(field, fieldName) { + if (field !== undefined) { + IssueError.generateAndThrowInternalError( + `Attempted to set ${fieldName} for schema tag ${this.longName} when it already has one.`, + ) } + return false } /** diff --git a/src/schema/schemaMerger.js b/src/schema/schemaMerger.js index 79d4c29f..13056f25 100644 --- a/src/schema/schemaMerger.js +++ b/src/schema/schemaMerger.js @@ -36,7 +36,7 @@ export default class PartneredSchemaMerger { */ _validate() { if (!this.sourceSchemas.every((schema) => schema.generation === 3)) { - IssueError.generateAndThrow('internalConsistencyError', { message: 'Partnered schemas must be HED-3G schemas' }) + IssueError.generateAndThrowInternalError('Partnered schemas must be HED-3G schemas') } for (const schema of this.sourceSchemas.slice(1)) { @@ -118,7 +118,7 @@ export default class PartneredSchemaMerger { if (rootedTagShortName) { const parentTag = tag.parent if (parentTag?.name?.toLowerCase() !== rootedTagShortName?.toLowerCase()) { - IssueError.generateAndThrow('internalError', { message: `Node ${shortName} is improperly rooted.` }) + IssueError.generateAndThrowInternalError(`Node ${shortName} is improperly rooted.`) } } diff --git a/src/utils/files.js b/src/utils/files.js index 37a07092..0665ee88 100644 --- a/src/utils/files.js +++ b/src/utils/files.js @@ -2,6 +2,8 @@ import fs from 'fs' import fetch from 'cross-fetch' +import { IssueError } from '../issues/issues' + /** * Read a local file. * @@ -29,7 +31,9 @@ export function readFile(fileName) { export async function readHTTPSFile(url) { const response = await fetch(url) if (!response.ok) { - throw new Error(`Server responded to ${url} with status code ${response.status}: ${response.statusText}`) + IssueError.generateAndThrowInternalError( + `Server responded to ${url} with status code ${response.status}: ${response.statusText}`, + ) } return response.text() } diff --git a/src/utils/memoizer.js b/src/utils/memoizer.js index b576042b..19b6d70e 100644 --- a/src/utils/memoizer.js +++ b/src/utils/memoizer.js @@ -32,9 +32,7 @@ export default class Memoizer { */ _memoize(propertyName, valueComputer) { if (!propertyName) { - IssueError.generateAndThrow('internalConsistencyError', { - message: 'Invalid property name in Memoizer subclass.', - }) + IssueError.generateAndThrowInternalError('Invalid property name in Memoizer subclass.') } if (this._memoizedProperties.has(propertyName)) { return this._memoizedProperties.get(propertyName)