uql-codemod 0.39.2 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/transform.js +64 -1
- package/package.json +1 -1
package/dist/transform.js
CHANGED
|
@@ -285,7 +285,8 @@ function foreignKeyColumn(property, column, relation, checker) {
|
|
|
285
285
|
}
|
|
286
286
|
const indent = ' '.repeat(property.getSourceFile().getLineAndCharacterOfPosition(property.getStart()).character);
|
|
287
287
|
const type = keyTypeSource(key, property, checker);
|
|
288
|
-
|
|
288
|
+
// `| null`, as on every column the codemod touches: a foreign key holds one like any other.
|
|
289
|
+
return inserted(property, `@Field({ references: () => ${target} }) ${column}?: ${type} | null;\n${indent}`);
|
|
289
290
|
}
|
|
290
291
|
/** The key's type as written where that is in scope, the same file, or as the checker spells it out. */
|
|
291
292
|
function keyTypeSource(key, at, checker) {
|
|
@@ -377,6 +378,7 @@ function rewriteEntityOptions(options, owner, ctx) {
|
|
|
377
378
|
}
|
|
378
379
|
for (const field of objectProperties(fields)) {
|
|
379
380
|
renameSqlOption(optionsOf(field.initializer), 'virtual', 'computed', field, owner, ctx);
|
|
381
|
+
admitNullOnDefined(field, owner, ctx);
|
|
380
382
|
}
|
|
381
383
|
for (const filter of objectProperties(propertyValue(options, 'filters'))) {
|
|
382
384
|
renameOption(optionsOf(filter.initializer), 'condition', 'where', filter, ctx);
|
|
@@ -669,6 +671,9 @@ function rewriteProperty(node, ctx) {
|
|
|
669
671
|
}
|
|
670
672
|
const options = decoratorOptions(decorator);
|
|
671
673
|
if (FIELD_DECORATORS.has(name)) {
|
|
674
|
+
if (name === 'Field') {
|
|
675
|
+
admitNull(options, node, ctx);
|
|
676
|
+
}
|
|
672
677
|
addFieldType(decorator, node, ctx);
|
|
673
678
|
// Options it cannot read may still hold `virtual`, so they are reported where they mention it.
|
|
674
679
|
if (options.kind === 'opaque' && /\bvirtual\b/.test(decorator.getText())) {
|
|
@@ -692,6 +697,64 @@ function rewriteProperty(node, ctx) {
|
|
|
692
697
|
}
|
|
693
698
|
unwrapRelationAlias(node, ctx);
|
|
694
699
|
}
|
|
700
|
+
/**
|
|
701
|
+
* A column holds `null` unless `nullable: false` says otherwise, and a read hydrates one, so the property
|
|
702
|
+
* says so too. A key is NOT NULL on every engine, and a relation aggregate is typed by the aggregate,
|
|
703
|
+
* which declares its own `null` or none, so neither is touched.
|
|
704
|
+
*/
|
|
705
|
+
function admitNull(options, node, ctx) {
|
|
706
|
+
// Options it cannot read may state `nullable` themselves, and appending to the property would then
|
|
707
|
+
// contradict the column. `addFieldType` already reports the same options, so this adds no second note.
|
|
708
|
+
if (options.kind === 'opaque') {
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (declaresNotNull(options) ||
|
|
712
|
+
findProperty(options, 'isId') ||
|
|
713
|
+
(findProperty(options, 'computed') && !findProperty(options, 'type'))) {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
if (!node.type) {
|
|
717
|
+
ctx.notes.push(`${ctx.describe(node)}: declare '${propertyKey(node.name)}' as a type that admits 'null', which the column holds`);
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
if (admitsNull(node.type)) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
ctx.edits.push(appended(node.type, ' | null'));
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* The same null rule where `defineEntity` names the field: the property is on the class the call takes,
|
|
727
|
+
* so the declaration is found there rather than under a decorator.
|
|
728
|
+
*/
|
|
729
|
+
function admitNullOnDefined(field, owner, ctx) {
|
|
730
|
+
if (!ts.isPropertyAssignment(field)) {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
const key = propertyKey(field.name);
|
|
734
|
+
const declaration = key
|
|
735
|
+
? instanceTypeOf(owner.entity, ctx.checker)
|
|
736
|
+
.getProperty(key)
|
|
737
|
+
?.declarations?.find((it) => ts.isPropertyDeclaration(it))
|
|
738
|
+
: undefined;
|
|
739
|
+
if (declaration?.type) {
|
|
740
|
+
admitNull(optionsOf(field.initializer), declaration, ctx);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
/** Whether the options say the column refuses `null`; `nullable: true` is the default said out loud. */
|
|
744
|
+
function declaresNotNull(options) {
|
|
745
|
+
const nullable = findProperty(options, 'nullable');
|
|
746
|
+
return !!nullable && ts.isPropertyAssignment(nullable) && nullable.initializer.kind === ts.SyntaxKind.FalseKeyword;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Whether a written type already admits `null`, wherever it sits in the union. In a type position `null`
|
|
750
|
+
* parses as a literal type rather than a keyword, which is why the kind is read off `literal`.
|
|
751
|
+
*/
|
|
752
|
+
function admitsNull(type) {
|
|
753
|
+
if (ts.isLiteralTypeNode(type)) {
|
|
754
|
+
return type.literal.kind === ts.SyntaxKind.NullKeyword;
|
|
755
|
+
}
|
|
756
|
+
return ts.isUnionTypeNode(type) && type.types.some(admitsNull);
|
|
757
|
+
}
|
|
695
758
|
/** Names a decorator that no longer exists, wherever it appears. */
|
|
696
759
|
function reportRemovedDecorators(node, ctx) {
|
|
697
760
|
for (const decorator of decoratorsOf(node)) {
|