UNPKG

graphql-codegen-flutter-freezed

Version:

A stand-alone package to generate Freezed models from GraphQL schema based on the flutter-freezed plugin for GraphQL Code Generator

247 lines (246 loc) 9.41 kB
import { FreezedDeclarationBlock } from './freezed-declaration-blocks'; export function transformDefinition(config, freezedFactoryBlockRepository, node) { // ignore these... if (['Query', 'Mutation', 'Subscription', ...(config?.ignoreTypes ?? [])].includes(node.name.value)) { return ''; } return new FreezedDeclarationBlock(config, freezedFactoryBlockRepository, node).init(); } /** * returns the value of the FreezedConfig option * for a specific type if given typeName * or else fallback to the global FreezedConfig value */ export function getFreezedConfigValue(option, config, typeName) { if (typeName) { return config?.typeSpecificFreezedConfig?.[typeName]?.config?.[option] ?? getFreezedConfigValue(option, config); } return config?.globalFreezedConfig?.[option]; } /** * @description filters the customDirectives to return those that are applied on a list of blocks */ export function getCustomDecorators(config, appliesOn, nodeName, fieldName) { const filteredCustomDecorators = {}; const globalCustomDecorators = config?.globalFreezedConfig?.customDecorators ?? {}; let customDecorators = { ...globalCustomDecorators }; if (nodeName) { const typeConfig = config?.typeSpecificFreezedConfig?.[nodeName]; const typeSpecificCustomDecorators = typeConfig?.config?.customDecorators ?? {}; customDecorators = { ...customDecorators, ...typeSpecificCustomDecorators }; if (fieldName) { const fieldSpecificCustomDecorators = typeConfig?.fields?.[fieldName]?.customDecorators ?? {}; customDecorators = { ...customDecorators, ...fieldSpecificCustomDecorators }; } } Object.entries(customDecorators).forEach(([key, value]) => value?.applyOn?.forEach(a => { if (appliesOn.includes(a)) { filteredCustomDecorators[key] = value; } })); return filteredCustomDecorators; } export function transformCustomDecorators(customDecorators, node, field) { let result = []; result = [ ...result, ...(node?.directives ?? []) .concat(field?.directives ?? []) // extract only the directives whose names were specified as keys // and have values that not undefined or null in the customDecorator record .filter(d => { const key = d.name.value; const value = customDecorators[key] ?? customDecorators[`@${key}`]; if (value && value.mapsToFreezedAs !== 'custom') { return true; } return false; }) // transform each directive to string .map(d => directiveToString(d, customDecorators)), ]; // for decorators that mapsToFreezedAs === 'custom' Object.entries(customDecorators).forEach(([key, value]) => { if (value.mapsToFreezedAs === 'custom') { const args = value?.arguments; // if the custom directives have arguments, if (args && args !== []) { // join them with a comma in the parenthesis result = [...result, `${key}(${args.join(', ')})`]; } else { // else return the customDecorator key just as it is result = [...result, key]; } } }); return result; } /** * transforms the directive into a decorator array * this decorator array might contain a `final` string which would be filtered out * and used to mark the parameter block as final */ function directiveToString(directive, customDecorators) { const key = directive.name.value; const value = customDecorators[key]; if (value.mapsToFreezedAs === 'directive') { // get the directive's arguments const directiveArgs = directive?.arguments ?? []; // extract the directive's argument using the template index: ["$0", "$1", ...] // specified in the customDecorator.arguments array const args = value?.arguments ?.filter(a => directiveArgs[argToInt(a)]) // transform the template index: ["$0", "$1", ...] into the arguments .map(a => directiveArgs[argToInt(a)]) // transform the arguments into string array of ["name: value" , "name: value", ...] .map(a => `${a.name}: ${a.value}`); // if the args is not empty if (args !== []) { // returns "@directiveName(argName: argValue, argName: argValue ...)" return `@${directive.name.value}(${args?.join(', ')})`; } } else if (value.mapsToFreezedAs === '@Default') { const defaultValue = directive?.arguments?.[argToInt(value?.arguments?.[0] ?? '0')]; if (defaultValue) { return `@Default(value: ${defaultValue})`; } } // returns either "@deprecated" || "final". // `final` to be filtered from the decorators array when applying the decorators return value.mapsToFreezedAs; } /** transforms string template: "$0" into an integer: 1 */ function argToInt(arg) { const parsedIndex = Number.parseInt(arg.replace('$', '').trim() ?? '0'); // '$1 => 1 return parsedIndex ? parsedIndex : 0; } /** a class variant of the getFreezedConfigValue helper function * * returns the value of the FreezedConfig option * for a specific type if given typeName * or else fallback to the global FreezedConfig value */ export class FreezedConfigValue { _config; _typeName; constructor(_config, _typeName) { this._config = _config; this._typeName = _typeName; this._config = _config; this._typeName = _typeName; } /** * returns the value of the FreezedConfig option * for a specific type if given typeName * or else fallback to the global FreezedConfig value */ get(option) { return getFreezedConfigValue(option, this._config, this._typeName); } } export class FreezedImportBlock { _config; _fileName; _importFreezedAnnotation = true; _importFoundation = true; _jsonSerializable; // TODO: the constructor should accept a node, and extract it shape and store it but return itself constructor(_config, _fileName) { this._config = _config; this._fileName = _fileName; } string() { return [ this._importFreezedAnnotation && "import 'package:freezed_annotation/freezed_annotation.dart';", this._importFoundation && "import 'package:flutter/foundation.dart';", `part ${this.getFileName(this._fileName)}.dart;`, this._jsonSerializable && `part '${this.getFileName(this._fileName)}.g.dart';`, ].join('\n'); } /** TODO: Work on the modularization * returns the fileName without the extension. * if modular is set to, returns the value of fileName from the config */ getFileName(fileName) { return this._config.modular ? this._config.fileName : fileName?.replace('.dart', ''); } } /** * stores an instance of FreezedFactoryBlock using the node names as the key * and returns that instance when replacing tokens * */ export class FreezedFactoryBlockRepository { _store = {}; register(key, value) { this._store[key] = value; return value; } retrieve(key, appliesOn, name, typeName) { return this._store[key] .setDecorators(appliesOn, key) .setKey(key) .setName(name) .setNamedConstructor(typeName) .init(); } } /** initializes a FreezedPluginConfig with the defaults values */ export class DefaultFreezedPluginConfig { customScalars; fileName; globalFreezedConfig; typeSpecificFreezedConfig; ignoreTypes; interfaceNamePrefix; interfaceNameSuffix; lowercaseEnums; modular; constructor(config = {}) { Object.assign(this, { customScalars: config.customScalars ?? {}, fileName: config.fileName ?? 'app_models', globalFreezedConfig: { ...new DefaultFreezedConfig(), ...(config.globalFreezedConfig ?? {}) }, typeSpecificFreezedConfig: config.typeSpecificFreezedConfig ?? {}, ignoreTypes: config.ignoreTypes ?? [], interfaceNamePrefix: config.interfaceNamePrefix ?? '', interfaceNameSuffix: config.interfaceNameSuffix ?? 'Interface', lowercaseEnums: config.lowercaseEnums ?? true, modular: config.modular ?? true, }); } } /** initializes a FreezedConfig with the defaults values */ export class DefaultFreezedConfig { alwaysUseJsonKeyName; copyWith; customDecorators; defaultUnionConstructor; equal; fromJsonToJson; immutable; makeCollectionsUnmodifiable; mergeInputs; mutableInputs; privateEmptyConstructor; unionKey; unionValueCase; constructor() { Object.assign(this, { alwaysUseJsonKeyName: false, copyWith: null, customDecorators: {}, defaultUnionConstructor: true, equal: null, fromJsonToJson: true, immutable: true, makeCollectionsUnmodifiable: null, mergeInputs: [], mutableInputs: true, privateEmptyConstructor: true, unionKey: null, unionValueCase: null, }); } }