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
JavaScript
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,
});
}
}