Skip to content

Commit

Permalink
feat: add @Suppress directive to suppress warnings, hints and compati…
Browse files Browse the repository at this point in the history
…bility issues
  • Loading branch information
Yogu committed Sep 5, 2024
1 parent 4c74454 commit e97bd1d
Show file tree
Hide file tree
Showing 35 changed files with 702 additions and 162 deletions.
93 changes: 93 additions & 0 deletions spec/schema/ast-validation-modules/suppress.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { assertValidatorAcceptsAndDoesNotWarn, assertValidatorWarns } from './helpers';

describe('@suppress', () => {
it('warns on type level if @suppress is not used', () => {
assertValidatorWarns(
`
type Stuff @rootEntity {
foo: String
}
type Child @childEntity {
stuff: Int
}
`,
'Type "Child" is not used.',
);
});

it('does not warn on type level if @suppress is used correctly with one entry', () => {
assertValidatorAcceptsAndDoesNotWarn(
`
type Stuff @rootEntity {
foo: String
}
type Child @childEntity @suppress(warnings: [UNUSED]) {
stuff: Int
}
`,
);
});

it('does not warn on type level if @suppress is used correctly with one non-list entry', () => {
assertValidatorAcceptsAndDoesNotWarn(
`
type Stuff @rootEntity {
foo: String
}
type Child @childEntity @suppress(warnings: UNUSED) {
stuff: Int
}
`,
);
});

it('does not warn on type level if @suppress is used correctly with multiple entries', () => {
assertValidatorAcceptsAndDoesNotWarn(
`
type Stuff @rootEntity {
foo: String
}
type Child @childEntity @suppress(warnings: [DEPRECATED, UNUSED]) {
stuff: Int
}
`,
);
});

it('warns on type level if @suppress is used with the wrong code entries', () => {
assertValidatorWarns(
`
type Stuff @rootEntity {
foo: String
}
type Child @childEntity @suppress(warnings: [DEPRECATED]) {
stuff: Int
}
`,
'Type "Child" is not used.',
);
});

it('warns on field level if @suppress is not used', () => {
assertValidatorWarns(
`
type Stuff @rootEntity {
foo: String
_key: ID @key
}
`,
'The field "_key" is deprecated and should be replaced with "id" (of type "ID").',
);
});

it('does not warn on field level if @suppress is used', () => {
assertValidatorAcceptsAndDoesNotWarn(
`
type Stuff @rootEntity {
foo: String
_key: ID @key @suppress(warnings: [DEPRECATED])
}
`,
);
});
});
11 changes: 8 additions & 3 deletions src/model/compatibility-check/check-calc-mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export function checkCalcMutations(
const operatorsDesc = operators.map((o) => o.toString()).join(', ');
const expectedDeclaration = `@calcMutations(operators: [${operatorsDesc}])`;
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'CALC_MUTATIONS',
`Field "${baselineField.declaringType.name}.${
baselineField.name
}" should be decorated with ${expectedDeclaration}${getRequiredBySuffix(
Expand Down Expand Up @@ -53,9 +54,13 @@ export function checkCalcMutations(
(a) => a.name.value === CALC_MUTATIONS_OPERATORS_ARG,
);
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'CALC_MUTATIONS',
`${message}${getRequiredBySuffix(baselineField)}.`,
operatorsAstNode ?? fieldToCheck.calcMutationsAstNode ?? fieldToCheck.astNode,
fieldToCheck.astNode,
{
location: operatorsAstNode ?? fieldToCheck.calcMutationsAstNode,
},
),
);
return;
Expand Down
37 changes: 23 additions & 14 deletions src/model/compatibility-check/check-collect-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@ import { getRequiredBySuffix } from './describe-module-specification';
/**
* Checks whether the @collect directives on the field and on the baseline fields match
*/
export function checkCollectField(fieldToCheck: Field, baselineField: Field, context: ValidationContext) {
export function checkCollectField(
fieldToCheck: Field,
baselineField: Field,
context: ValidationContext,
) {
// superfluous @collect
if (fieldToCheck.isCollectField && !baselineField.isCollectField) {
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'COLLECT',
`Field "${baselineField.declaringType.name}.${
baselineField.name
}" should not be a collect field${getRequiredBySuffix(baselineField)}.`,
fieldToCheck.collectAstNode,
fieldToCheck.astNode,
{ location: fieldToCheck.collectAstNode },
),
);
return;
Expand All @@ -35,7 +41,8 @@ export function checkCollectField(fieldToCheck: Field, baselineField: Field, con
expectedAggregateDeclaration ? ', ' + expectedAggregateDeclaration : ''
})`;
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'COLLECT',
`Field "${baselineField.declaringType.name}.${
baselineField.name
}" should be decorated with ${expectedCollectDeclaration}${getRequiredBySuffix(
Expand All @@ -54,13 +61,13 @@ export function checkCollectField(fieldToCheck: Field, baselineField: Field, con
fieldToCheck.collectPath.path !== baselineField.collectPath.path
) {
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'COLLECT',
`Path should be "${baselineField.collectPath.path}"${getRequiredBySuffix(
baselineField,
)}.`,
fieldToCheck.collectPathAstNode ??
fieldToCheck.collectAstNode ??
fieldToCheck.astNode,
fieldToCheck.astNode,
{ location: fieldToCheck.collectPathAstNode ?? fieldToCheck.collectAstNode },
),
);
return;
Expand All @@ -69,9 +76,11 @@ export function checkCollectField(fieldToCheck: Field, baselineField: Field, con
// superfluous aggregate
if (fieldToCheck.aggregationOperator && !baselineField.aggregationOperator) {
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'COLLECT',
`No aggregation should be used here${getRequiredBySuffix(baselineField)}.`,
fieldToCheck.aggregationOperatorAstNode ?? fieldToCheck.astNode,
fieldToCheck.astNode,
{ location: fieldToCheck.aggregationOperatorAstNode },
),
);
}
Expand All @@ -82,13 +91,13 @@ export function checkCollectField(fieldToCheck: Field, baselineField: Field, con
fieldToCheck.aggregationOperator !== baselineField.aggregationOperator
) {
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'COLLECT',
`Collect field should specify ${expectedAggregateDeclaration}${getRequiredBySuffix(
baselineField,
)}.`,
fieldToCheck.inverseOfAstNode ??
fieldToCheck.relationAstNode ??
fieldToCheck.astNode,
fieldToCheck.astNode,
{ location: fieldToCheck.aggregationOperatorAstNode },
),
);
}
Expand Down
14 changes: 10 additions & 4 deletions src/model/compatibility-check/check-default-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ export function checkDefaultValue(
!baselineField.hasDefaultValue
) {
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'DEFAULT_VALUE',
`Field "${baselineField.declaringType.name}.${
baselineField.name
}" should not have a default value${getRequiredBySuffix(baselineField)}.`,
fieldToCheck.defaultValueAstNode ?? fieldToCheck.astNode,
fieldToCheck.astNode,
{ location: fieldToCheck.defaultValueAstNode },
),
);
return;
Expand All @@ -41,13 +43,15 @@ export function checkDefaultValue(
if (!fieldToCheck.hasDefaultValue) {
const expectedDeclaration = `@defaultValue(value: ${expectedDefaultValue})`;
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'DEFAULT_VALUE',
`Field "${baselineField.declaringType.name}.${
baselineField.name
}" should be decorated with ${expectedDeclaration}${getRequiredBySuffix(
baselineField,
)}.`,
fieldToCheck.astNode,
{ location: fieldToCheck.defaultValueAstNode },
),
);
return;
Expand All @@ -56,11 +60,13 @@ export function checkDefaultValue(
// wrong default value
if (!deepEqual(fieldToCheck.defaultValue, baselineField.defaultValue)) {
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'DEFAULT_VALUE',
`Default value should be ${expectedDefaultValue}${getRequiredBySuffix(
baselineField,
)}.`,
fieldToCheck.astNode,
{ location: fieldToCheck.defaultValueAstNode },
),
);
return;
Expand Down
6 changes: 4 additions & 2 deletions src/model/compatibility-check/check-enum-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ export function checkEnumType(
const matchingValue = typeToCheck.values.find((v) => v.value === baselineValue.value);
if (!matchingValue) {
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'MISSING_ENUM_VALUE',
`Enum value "${baselineValue.value}" is missing${getRequiredBySuffix(
baselineType,
)}.`,
typeToCheck.nameASTNode,
typeToCheck.astNode,
{ location: typeToCheck.nameASTNode },
),
);
}
Expand Down
30 changes: 21 additions & 9 deletions src/model/compatibility-check/check-field-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,32 +31,44 @@ export function checkFieldType(

if (fieldToCheck.type.name !== baselineField.type.name) {
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'FIELD_TYPE',
`Field "${baselineField.declaringType.name}.${
baselineField.name
}" needs to be of type "${expectedType}"${getRequiredBySuffix(baselineField)}.`,
fieldToCheck.astNode?.type,
{ quickFixes },
fieldToCheck.astNode,
{
location: fieldToCheck.astNode?.type,
quickFixes,
},
),
);
} else if (fieldToCheck.isList && !baselineField.isList) {
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'FIELD_TYPE',
`Field "${baselineField.declaringType.name}.${
baselineField.name
}" should not be a list${getRequiredBySuffix(baselineField)}.`,
fieldToCheck.astNode?.type,
{ quickFixes },
fieldToCheck.astNode,
{
location: fieldToCheck.astNode?.type,
quickFixes,
},
),
);
} else if (!fieldToCheck.isList && baselineField.isList) {
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'FIELD_TYPE',
`Field "${baselineField.declaringType.name}.${
baselineField.name
}" needs to be a list${getRequiredBySuffix(baselineField)}.`,
fieldToCheck.astNode?.type,
{ quickFixes },
fieldToCheck.astNode,
{
location: fieldToCheck.astNode?.type,
quickFixes,
},
),
);
}
Expand Down
9 changes: 6 additions & 3 deletions src/model/compatibility-check/check-key-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,19 @@ export function checkKeyField(
// (because it's an automatic system field). We need to tell the user that the field needs to be added.
if (fieldToCheck.isSystemField && fieldToCheck.name === ID_FIELD && !fieldToCheck.astNode) {
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'MISSING_FIELD',
`Field "id: ID @key" needs to be specified${getRequiredBySuffix(
baselineField,
)}.`,
fieldToCheck.declaringType.nameASTNode,
fieldToCheck.declaringType.astNode,
{ location: fieldToCheck.declaringType.nameASTNode },
),
);
} else {
context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'KEY_FIELD',
`Field "${baselineField.declaringType.name}.${
baselineField.name
}" needs to be decorated with @key${getRequiredBySuffix(baselineField)}.`,
Expand Down
2 changes: 1 addition & 1 deletion src/model/compatibility-check/check-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function checkModel(modelToCheck: Model, baselineModel: Model): Validatio
}

context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.nonSuppressableCompatibilityIssue(
`Type "${baselineType.name}" is missing${getRequiredBySuffix(baselineType)}.`,
undefined,
{ quickFixes },
Expand Down
7 changes: 4 additions & 3 deletions src/model/compatibility-check/check-object-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,13 @@ export function checkObjectType(
}

context.addMessage(
ValidationMessage.compatibilityIssue(
ValidationMessage.suppressableCompatibilityIssue(
'MISSING_FIELD',
`Field "${baselineType.name}.${
baselineField.name
}" is missing${getRequiredBySuffix(baselineField)}.`,
typeToCheck.nameASTNode,
{ quickFixes },
typeToCheck.astNode,
{ location: typeToCheck.nameASTNode, quickFixes },
),
);
continue;
Expand Down
Loading

0 comments on commit e97bd1d

Please sign in to comment.