From a4cca88803bc1960fbdeac257230578aeccf764c Mon Sep 17 00:00:00 2001 From: Guilherme Mierzwa Date: Thu, 19 Oct 2023 05:14:10 -0300 Subject: [PATCH] fix: make order of imports and object properties deterministic (#965) --- packages/core/src/generators/imports.ts | 22 +++- packages/core/src/getters/object.ts | 162 ++++++++++++------------ packages/msw/src/getters/object.ts | 3 + packages/msw/src/getters/scalar.ts | 18 ++- 4 files changed, 118 insertions(+), 87 deletions(-) diff --git a/packages/core/src/generators/imports.ts b/packages/core/src/generators/imports.ts index 018b22873..e5b4be0c7 100644 --- a/packages/core/src/generators/imports.ts +++ b/packages/core/src/generators/imports.ts @@ -140,7 +140,9 @@ const generateDependency = ({ deps .filter((e) => !e.default && !e.syntheticDefaultImport) .map(({ name, alias }) => (alias ? `${name} as ${alias}` : name)), - ).join(',\n '); + ) + .sort() + .join(',\n '); let importString = ''; @@ -246,6 +248,11 @@ export const addDependency = ({ .join('\n'); }; +const getLibName = (code: string) => { + const splitString = code.split(' from '); + return splitString[splitString.length - 1].split(';')[0].trim(); +}; + export const generateDependencyImports = ( implementation: string, imports: { @@ -267,6 +274,19 @@ export const generateDependencyImports = ( }), ) .filter(Boolean) + .sort((a, b) => { + const aLib = getLibName(a!); + const bLib = getLibName(b!); + + if (aLib === bLib) { + return 0; + } + + if (aLib.startsWith("'.") && !bLib.startsWith("'.")) { + return 1; + } + return aLib < bLib ? -1 : 1; + }) .join('\n'); return dependencies ? dependencies + '\n' : ''; diff --git a/packages/core/src/getters/object.ts b/packages/core/src/getters/object.ts index 9d1df1d9a..50df4e14a 100644 --- a/packages/core/src/getters/object.ts +++ b/packages/core/src/getters/object.ts @@ -58,90 +58,94 @@ export const getObject = ({ } if (item.properties && Object.entries(item.properties).length > 0) { - return Object.entries(item.properties).reduce( - ( - acc, - [key, schema]: [string, ReferenceObject | SchemaObject], - index, - arr, - ) => { - const isRequired = ( - Array.isArray(item.required) ? item.required : [] - ).includes(key); - - let propName = ''; - - if (name) { - const isKeyStartWithUnderscore = key.startsWith('_'); - - propName += pascal( - `${isKeyStartWithUnderscore ? '_' : ''}${name}_${key}`, + return Object.entries(item.properties) + .sort((a, b) => { + return a[0].localeCompare(b[0]); + }) + .reduce( + ( + acc, + [key, schema]: [string, ReferenceObject | SchemaObject], + index, + arr, + ) => { + const isRequired = ( + Array.isArray(item.required) ? item.required : [] + ).includes(key); + + let propName = ''; + + if (name) { + const isKeyStartWithUnderscore = key.startsWith('_'); + + propName += pascal( + `${isKeyStartWithUnderscore ? '_' : ''}${name}_${key}`, + ); + } + + const allSpecSchemas = + context.specs[context.target]?.components?.schemas ?? {}; + + const isNameAlreadyTaken = Object.keys(allSpecSchemas).some( + (schemaName) => pascal(schemaName) === propName, ); - } - - const allSpecSchemas = - context.specs[context.target]?.components?.schemas ?? {}; - - const isNameAlreadyTaken = Object.keys(allSpecSchemas).some( - (schemaName) => pascal(schemaName) === propName, - ); - - if (isNameAlreadyTaken) { - propName = propName + 'Property'; - } - - const resolvedValue = resolveObject({ - schema, - propName, - context, - }); - - const isReadOnly = item.readOnly || (schema as SchemaObject).readOnly; - if (!index) { - acc.value += '{'; - } - - const doc = jsDoc(schema as SchemaObject, true); - - acc.hasReadonlyProps ||= isReadOnly || false; - acc.imports.push(...resolvedValue.imports); - acc.value += `\n ${doc ? `${doc} ` : ''}${ - isReadOnly ? 'readonly ' : '' - }${getKey(key)}${isRequired ? '' : '?'}: ${resolvedValue.value};`; - acc.schemas.push(...resolvedValue.schemas); - - if (arr.length - 1 === index) { - if (item.additionalProperties) { - if (isBoolean(item.additionalProperties)) { - acc.value += `\n [key: string]: any;\n }`; + + if (isNameAlreadyTaken) { + propName = propName + 'Property'; + } + + const resolvedValue = resolveObject({ + schema, + propName, + context, + }); + + const isReadOnly = item.readOnly || (schema as SchemaObject).readOnly; + if (!index) { + acc.value += '{'; + } + + const doc = jsDoc(schema as SchemaObject, true); + + acc.hasReadonlyProps ||= isReadOnly || false; + acc.imports.push(...resolvedValue.imports); + acc.value += `\n ${doc ? `${doc} ` : ''}${ + isReadOnly ? 'readonly ' : '' + }${getKey(key)}${isRequired ? '' : '?'}: ${resolvedValue.value};`; + acc.schemas.push(...resolvedValue.schemas); + + if (arr.length - 1 === index) { + if (item.additionalProperties) { + if (isBoolean(item.additionalProperties)) { + acc.value += `\n [key: string]: any;\n }`; + } else { + const resolvedValue = resolveValue({ + schema: item.additionalProperties, + name, + context, + }); + acc.value += `\n [key: string]: ${resolvedValue.value};\n}`; + } } else { - const resolvedValue = resolveValue({ - schema: item.additionalProperties, - name, - context, - }); - acc.value += `\n [key: string]: ${resolvedValue.value};\n}`; + acc.value += '\n}'; } - } else { - acc.value += '\n}'; - } - acc.value += nullable; - } + acc.value += nullable; + } - return acc; - }, - { - imports: [], - schemas: [], - value: '', - isEnum: false, - type: 'object' as SchemaType, - isRef: false, - schema: {}, - hasReadonlyProps: false, - } as ScalarValue, - ); + return acc; + }, + { + imports: [], + schemas: [], + value: '', + isEnum: false, + type: 'object' as SchemaType, + isRef: false, + schema: {}, + hasReadonlyProps: false, + } as ScalarValue, + ); } if (item.additionalProperties) { diff --git a/packages/msw/src/getters/object.ts b/packages/msw/src/getters/object.ts index bedf5ffc3..a1ee9815a 100644 --- a/packages/msw/src/getters/object.ts +++ b/packages/msw/src/getters/object.ts @@ -72,6 +72,9 @@ export const getMockObject = ({ let imports: GeneratorImport[] = []; let includedProperties: string[] = []; value += Object.entries(item.properties) + .sort((a, b) => { + return a[0].localeCompare(b[0]); + }) .map(([key, prop]: [string, ReferenceObject | SchemaObject]) => { if (combine?.includedProperties.includes(key)) { return undefined; diff --git a/packages/msw/src/getters/scalar.ts b/packages/msw/src/getters/scalar.ts index 75e4ad8a6..11460e944 100644 --- a/packages/msw/src/getters/scalar.ts +++ b/packages/msw/src/getters/scalar.ts @@ -46,13 +46,17 @@ export const getMockScalar = ({ return operationProperty; } - const overrideTag = Object.entries(mockOptions?.tags ?? {}).reduce<{ - properties: Record; - }>( - (acc, [tag, options]) => - tags.includes(tag) ? mergeDeep(acc, options) : acc, - {} as { properties: Record }, - ); + const overrideTag = Object.entries(mockOptions?.tags ?? {}) + .sort((a, b) => { + return a[0].localeCompare(b[0]); + }) + .reduce<{ + properties: Record; + }>( + (acc, [tag, options]) => + tags.includes(tag) ? mergeDeep(acc, options) : acc, + {} as { properties: Record }, + ); const tagProperty = resolveMockOverride(overrideTag?.properties, item);