Skip to content
This repository has been archived by the owner on Dec 13, 2023. It is now read-only.

validateProps #94

Closed
wants to merge 30 commits into from
Closed

Conversation

AmaranthineCodices
Copy link
Contributor

@AmaranthineCodices AmaranthineCodices commented May 15, 2018

Closes #24. This is an integration of PropTypes in Roact.

TODO:

  • Gate type checks behind a configuration flag
  • Document this

This depends on #93.

@coveralls
Copy link

coveralls commented May 15, 2018

Coverage Status

Coverage increased (+0.2%) to 90.374% when pulling 8f7d97b on AmaranthineCodices:propTypes into 077672b on Roblox:master.

@LPGhatguy
Copy link
Contributor

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 apply or validate method -- just functions would work well!

I imagine the signature for a prop validator function would be (props, propKey, component) -> success, result.

Usage would still be:

Component.propTypes = {
    foo = PropTypes.number, -- or any function of the form above
}

@AmaranthineCodices
Copy link
Contributor Author

Some further notes about this:

  • Hard dependency on Add stackTrace getter #93 in its current form, where the stack trace method is named rootElementTraceback.
  • Another stack trace is collected when the component is created; this is used to provide information about what component to look at when propTypes is not a function.
  • Compatible with any validation function that has the signature value -> success, reason.
  • Default traceback message has been moved from the reconciler to Core under the name _defaultSource. It may need a better name; I'm not sure!

@AmaranthineCodices AmaranthineCodices changed the title WIP: PropTypes implementation validateProps May 23, 2018
@AmaranthineCodices
Copy link
Contributor Author

This should be ready to go now!

Copy link
Contributor

@LPGhatguy LPGhatguy left a 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
Copy link
Contributor

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?

Copy link
Contributor Author

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 >.<

```

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.
Copy link
Contributor

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,
Copy link
Contributor

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.

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,
Copy link
Contributor

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"
Copy link
Contributor

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. 🤔

Performs property validation, if it is enabled.
]]
function Component:_validateProps(props)
if not GlobalConfig.getValue("propertyValidation") then return end
Copy link
Contributor

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

-- 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(
Copy link
Contributor

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!

GlobalConfig.reset()
end)

it("should not be run if typeChecking is false", function()
Copy link
Contributor

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

Copy link
Contributor Author

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
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, that makes sense!

@LPGhatguy
Copy link
Contributor

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?

@AmaranthineCodices
Copy link
Contributor Author

AmaranthineCodices commented Jun 5, 2018

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.


local validator = self.validateProps

if validator == nil then return end
Copy link
Contributor

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!

@LPGhatguy
Copy link
Contributor

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.

@AmaranthineCodices
Copy link
Contributor Author

Alright! I've gotten this back to mergeable as of now; I think this should be pretty easy to port to the new reconciler!

@AmaranthineCodices
Copy link
Contributor Author

Closing in favor of #174 targeted at the new reconciler.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants