-
Notifications
You must be signed in to change notification settings - Fork 24.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: The [PlatformColor PR](#27908) added support for iOS and Android to express platform specific color values. The primary method for an app to specify such colors is via the `PlatformColor()` method that takes string arguments. The `PlatformColor` method returns an opaque Flow type enforcing that apps use the PlatformColor method instead of creating Objects from scratch -- doing so would make it harder to write static analysis tools around Color values in the future. But in addition to `PlatformColor()`, iOS has a `DynamicColorIOS()` method that takes an Object. The Flow type for this Object cannot be opaque, but we still want to enforce that app code doesn't pass variables instead of Object literals or that values in the Objects are variables. To ensure `DynamicColorIOS()` can be statically analyzed this change adds an ESLint rule to enforce that `DynamicColorIOS()` takes an Object literal of a specific shape. A `ColorAndroid()` was also introduced not for practical use but just to test having platform specific methods for more than one platform in the same app. A second ESLint rule is created for `ColorAndroid` as well. ## Changelog [General] [Changed] - Add ES Lint rules for `DynamicColorIOS()`and `ColorAndroid()` Pull Request resolved: #28398 Test Plan: `yarn lint` passes. Reviewed By: cpojer Differential Revision: D20685383 Pulled By: TheSavior fbshipit-source-id: 9bb37ccc059e74282b119577df0ced63cb9b1f53
- Loading branch information
1 parent
1281be6
commit 602070f
Showing
5 changed files
with
195 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
packages/eslint-plugin-react-native-community/__tests__/platform-colors-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @emails oncall+react_native | ||
* @format | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const ESLintTester = require('./eslint-tester.js'); | ||
|
||
const rule = require('../platform-colors.js'); | ||
|
||
const eslintTester = new ESLintTester(); | ||
|
||
eslintTester.run('../platform-colors', rule, { | ||
valid: [ | ||
"const color = PlatformColor('labelColor');", | ||
"const color = PlatformColor('controlAccentColor', 'controlColor');", | ||
"const color = DynamicColorIOS({light: 'black', dark: 'white'});", | ||
"const color = DynamicColorIOS({light: PlatformColor('black'), dark: PlatformColor('white')});", | ||
"const color = ColorAndroid('?attr/colorAccent')", | ||
], | ||
invalid: [ | ||
{ | ||
code: 'const color = PlatformColor();', | ||
errors: [{message: rule.meta.messages.platformColorArgsLength}], | ||
}, | ||
{ | ||
code: | ||
"const labelColor = 'labelColor'; const color = PlatformColor(labelColor);", | ||
errors: [{message: rule.meta.messages.platformColorArgTypes}], | ||
}, | ||
{ | ||
code: | ||
"const tuple = {light: 'black', dark: 'white'}; const color = DynamicColorIOS(tuple);", | ||
errors: [{message: rule.meta.messages.dynamicColorIOSArg}], | ||
}, | ||
{ | ||
code: | ||
"const black = 'black'; const color = DynamicColorIOS({light: black, dark: 'white'});", | ||
errors: [{message: rule.meta.messages.dynamicColorIOSLight}], | ||
}, | ||
{ | ||
code: | ||
"const white = 'white'; const color = DynamicColorIOS({light: 'black', dark: white});", | ||
errors: [{message: rule.meta.messages.dynamicColorIOSDark}], | ||
}, | ||
{ | ||
code: 'const color = ColorAndroid();', | ||
errors: [{message: rule.meta.messages.colorAndroidArg}], | ||
}, | ||
{ | ||
code: | ||
"const colorAccent = '?attr/colorAccent'; const color = ColorAndroid(colorAccent);", | ||
errors: [{message: rule.meta.messages.colorAndroidArg}], | ||
}, | ||
], | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
packages/eslint-plugin-react-native-community/platform-colors.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @format | ||
*/ | ||
|
||
module.exports = { | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: | ||
'Ensure that PlatformColor(), DynamicColorIOS(), and ColorAndroid() are passed literals of the expected shape.', | ||
}, | ||
messages: { | ||
platformColorArgsLength: | ||
'PlatformColor() must have at least one argument that is a literal.', | ||
platformColorArgTypes: | ||
'PlatformColor() every argument must be a literal.', | ||
dynamicColorIOSArg: | ||
'DynamicColorIOS() must take a single argument of type Object containing two keys: light and dark.', | ||
dynamicColorIOSLight: | ||
'DynamicColorIOS() light value must be either a literal or a PlatformColor() call.', | ||
dynamicColorIOSDark: | ||
'DynamicColorIOS() dark value must be either a literal or a PlatformColor() call.', | ||
colorAndroidArg: | ||
'ColorAndroid() must take a single argument that is a literal.', | ||
}, | ||
schema: [], | ||
}, | ||
|
||
create: function(context) { | ||
return { | ||
CallExpression: function(node) { | ||
if (node.callee.name === 'PlatformColor') { | ||
const args = node.arguments; | ||
if (args.length === 0) { | ||
context.report({ | ||
node, | ||
messageId: 'platformColorArgsLength', | ||
}); | ||
return; | ||
} | ||
if (!args.every(arg => arg.type === 'Literal')) { | ||
context.report({ | ||
node, | ||
messageId: 'platformColorArgTypes', | ||
}); | ||
return; | ||
} | ||
} else if (node.callee.name === 'DynamicColorIOS') { | ||
const args = node.arguments; | ||
if (!(args.length === 1 && args[0].type === 'ObjectExpression')) { | ||
context.report({ | ||
node, | ||
messageId: 'dynamicColorIOSArg', | ||
}); | ||
return; | ||
} | ||
const properties = args[0].properties; | ||
if ( | ||
!( | ||
properties.length === 2 && | ||
properties[0].type === 'Property' && | ||
properties[0].key.name === 'light' && | ||
properties[1].type === 'Property' && | ||
properties[1].key.name === 'dark' | ||
) | ||
) { | ||
context.report({ | ||
node, | ||
messageId: 'dynamicColorIOSArg', | ||
}); | ||
return; | ||
} | ||
const light = properties[0]; | ||
if ( | ||
!( | ||
light.value.type === 'Literal' || | ||
(light.value.type === 'CallExpression' && | ||
light.value.callee.name === 'PlatformColor') | ||
) | ||
) { | ||
context.report({ | ||
node, | ||
messageId: 'dynamicColorIOSLight', | ||
}); | ||
return; | ||
} | ||
const dark = properties[1]; | ||
if ( | ||
!( | ||
dark.value.type === 'Literal' || | ||
(dark.value.type === 'CallExpression' && | ||
dark.value.callee.name === 'PlatformColor') | ||
) | ||
) { | ||
context.report({ | ||
node, | ||
messageId: 'dynamicColorIOSDark', | ||
}); | ||
return; | ||
} | ||
} else if (node.callee.name === 'ColorAndroid') { | ||
const args = node.arguments; | ||
if (!(args.length === 1 && args[0].type === 'Literal')) { | ||
context.report({ | ||
node, | ||
messageId: 'colorAndroidArg', | ||
}); | ||
return; | ||
} | ||
} | ||
}, | ||
}; | ||
}, | ||
}; |