-
Notifications
You must be signed in to change notification settings - Fork 143
Conversation
I think the prop types implementation, no matter which one we use, should just have a functional interface to maximize the ability to support multiple implementations and custom validators. We probably don't need an I imagine the signature for a prop validator function would be Usage would still be: Component.propTypes = {
foo = PropTypes.number, -- or any function of the form above
} |
Some further notes about this:
|
This should be ready to go now! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like where this is going and I'm super excited to get this feature in!
.gitmodules
Outdated
@@ -3,4 +3,4 @@ | |||
url = https://github.com/LPGhatguy/lemur.git | |||
[submodule "modules/testez"] | |||
path = modules/testez | |||
url = https://github.com/Roblox/testez.git | |||
url = https://github.com/Roblox/testez.git |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we revert this change in this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I...can try; this is an artifact of removing PropTypes
. I'm not sure I'll be able to without borking the branch though. Submodules are weird >.<
docs/api-reference.md
Outdated
``` | ||
|
||
Performs property validation. You can use this for type-checking properties; there is a [PropTypes](https://github.com/AmaranthineCodices/rbx-prop-types) library to assist in this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should avoid calling props 'properties' except in their initial introduction. The (shortened) word is used intentionally to explicitly refer to the React and Roact concept as opposed to the general idea of properties.
When we introduce your library, let's say something like "there is a library called PropTypes that can be used to validate props declaratively"
lib/Config.lua
Outdated
@@ -18,6 +18,8 @@ local defaultConfig = { | |||
["elementTracing"] = false, | |||
-- Enables instrumentation of shouldUpdate and render methods for Roact components | |||
["componentInstrumentation"] = false, | |||
-- Enables type checking with the validateProps property on stateful components. | |||
["propertyValidation"] = false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much like the recommendation in the docs, let's call this one propValidation
to be more exact.
lib/Component.lua
Outdated
if not success then | ||
failureReason = failureReason or "<No failure reason was given by the validation function>" | ||
error(("Property validation failed:\n%s\n%s"):format( | ||
failureReason, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should explicitly call tostring
on the failure reason in case the validator wants to express more detailed errors in some cases.
lib/Core.lua
Outdated
@@ -25,6 +25,9 @@ Core.None = Symbol.named("None") | |||
-- Marker used to specify that the table it is present within is a component. | |||
Core.Element = Symbol.named("Element") | |||
|
|||
-- The default "stack traceback" if element tracing is not enabled. | |||
Core._defaultSource = "\n\t<Use Roact.setGlobalConfig with the 'elementTracing' key to enable detailed tracebacks>\n" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could probably stand to have a better name, especially now that it's left the scope of the reconciler! Maybe something like _elementTracebacksDisabledMessage
?
I'm starting to feel that we should collect some of the error messages together into a module in a future PR. 🤔
lib/Component.lua
Outdated
Performs property validation, if it is enabled. | ||
]] | ||
function Component:_validateProps(props) | ||
if not GlobalConfig.getValue("propertyValidation") then return end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep these types blocks to one statement per line, which I think makes them a little easier to read with how keyword-noisy Lua is:
if not GlobalConfig.getValue("propertyValidation") then
return
end
lib/Component.lua
Outdated
-- Hide as much as possible about error location, since this message | ||
-- occurs from several possible call sites with different stack traces. | ||
-- luacheck: ignore 6 | ||
error(("The value of validateProps must be a function (is a %q). Check the definition of the component extended at:\n%s"):format( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we tweak this error message?
"The value of validateProps must be a function, but it is a %s." ..
"\nCheck the component defined at:\n%s"
Maybe this is a case to bring in multilineError
or whatever I've been calling it lately!
lib/Component.spec.lua
Outdated
GlobalConfig.reset() | ||
end) | ||
|
||
it("should not be run if typeChecking is false", function() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the test name needs to be updated to refer to the config name propValidation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, yeah! Fixed now.
typeof(validator), | ||
self._extendTraceback), | ||
0) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this totally necessary? We don't bother doing this with lifecycle methods or things like defaultProps
(making sure it's a table).
I can see the argument for doing it here since you're already opting into validation, just wondering what the thought process is since it's not something we do elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the time when validateProps
is specified, it's not being written as a method:
function SomeComponent.validateProps(props)
-- do some validation work
end
Instead, it's usually composed from a bunch of smaller validators, and the result of the composition is assigned. When you're using PropTypes it looks something like this:
SomeComponent.validateProps = PropTypes.object {
someKey = PropTypes.number,
someOtherKey = PropTypes.string,
}
The type check is an extra guard against validateProps
being assigned a non-function, since it's probably not going to be declared as a method of the component class, but as a field of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, that makes sense!
An interesting thing that popped into my mind: does gating this behind a global flag make unit testing harder? Naturally, you want to run all tests with typechecking enabled and also develop with typechecking enabled, but how do you best represent that with the config system only (rightfully?) letting you set configuration once? |
I'm not entirely sure it blocks unit testing, to be honest, though that may be an artifact of how I run my unit tests. My tests don't run at the same time as the main entry point (they have their own entry point), so the configuration can be set once per entry point without issue. WRT this: should we hold off on merging until after the new reconciler? I can migrate this to the new one once it's landed, or we can merge it now and reimplement validation. |
lib/Component.lua
Outdated
|
||
local validator = self.validateProps | ||
|
||
if validator == nil then return end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we put return
on its own line? We try to follow one statement per line!
I think we should push to get this merged; I'll cover any nitpicks I have either with commits to your PR branch directly or in commits afterwards. I bet we can make this change really small to minimize the impact and make porting to any new reconciler a lot easier. |
Alright! I've gotten this back to mergeable as of now; I think this should be pretty easy to port to the new reconciler! |
Closing in favor of #174 targeted at the new reconciler. |
Closes #24. This is an integration of PropTypes in Roact.
TODO:
This depends on #93.