Reputation: 8993
I have the following "visual test" (aka, I'm just using vs-code to visually inspect whether there are or are not red squiggly lines under things). It's intent is to ensure that the generic type that I pass in does indeed constrain the called to substitute()
:
interface IJob {
jobId: string;
extraneous?: number;
}
/**
* Bear in mind these tests are just to visually identify in
* the editor that the generics are passing their types around
* correctly.
*/
describe("Typings → ", () => {
it.skip('setting <T> imposes structure on the substitutions that are allowed in', () => {
const wrongType = {
jobId: "1234",
extraneous: "foobar"
}
const nonExistantType = {
jobId: "1234",
noCanDo: true
}
const ruleAbidingJob = {
jobId: "1234"
}
const t = TypedTemplate.create<IJob>();
t.substitute(wrongType); // this should be failing in TS
t.substitute(nonExistantType); // this should be failing in TS
t.substitute(ruleAbidingJob); // this should be fine
});
});
It does seem to allow for the ruleAbidingJob
and recognize correctly that wrongType
has set extraneous to a string instead of a number as the interface defines it. Unfortunately it DOES allow nonExistantType
to be used without error.
Here's the visual if that helps:
and also here's a screen shot showing that indeed the expected type for substitute
is indeed IJob:
Upvotes: 0
Views: 106
Reputation: 249506
Typescript considers the type containing the extra property (the type of nonExistantType
) to be a subtype of IJob
and this is why the call is allowed. You can consider the function parameter type to be the minimal set of properties the function needs to work, so if the passed in argument has more properties the function should not care.
Typescript does have a feature that marks extra properties as an error, but only when an object literal is directly assigned to a variable/argument of a specific type. In your case you can either pass the object literal directly to the function, or explicitly type the constant:
const nonExistantType :IJob = { // Error
jobId: "1234",
noCanDo: true
}
// OR
t.substitute({
jobId: "1234",
noCanDo: true // error
})
Edit
We can also check that there are no extra properties, post object creation, by using a conditional type in typescript 2.8:
const wrongType = {
jobId: "1234",
extraneous: "foobar"
}
const nonExistantType = {
jobId: "1234",
noCanDo: true
}
const ruleAbidingJob = {
jobId: "1234"
}
type NoExtraProperties<TSource, TTarget> = Exclude<keyof TSource, keyof TTarget> extends never ? true : "Extra properties detected";
function ensueNothingExtra<TSource, TTarget>(source : TSource, validation: NoExtraProperties<TSource, TTarget>) :TTarget {
return source as any
}
const t = TypedTemplate.create<IJob>();
t.substitute(ensueNothingExtra(wrongType, true)); // this is an error
t.substitute(ensueNothingExtra(nonExistantType, true)); // this is also an error
t.substitute(ensueNothingExtra(ruleAbidingJob, true)); // this is ok
Upvotes: 1