init source

This commit is contained in:
Le Viet
2022-03-07 22:07:57 +07:00
parent e4376f3777
commit 8aba590a8d
11240 changed files with 1012977 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
{
"presets": ["env"],
"plugins": [
["transform-replace-object-assign", { "moduleSpecifier": "object.assign" }],
],
}
+3
View File
@@ -0,0 +1,3 @@
node_modules/
reports/
lib/
+3
View File
@@ -0,0 +1,3 @@
{
extends: "airbnb-base"
}
+39
View File
@@ -0,0 +1,39 @@
language: node_js
os:
- linux
node_js:
- "12"
- "10"
- "8"
- "6"
- "4"
- "0.12"
- "0.10"
cache:
directories:
- node_modules
after_success:
- npm run coveralls
before_install:
- 'case "${TRAVIS_NODE_VERSION}" in 0.*) export NPM_CONFIG_STRICT_SSL=false ;; esac'
- 'nvm install-latest-npm'
install:
- 'if [ "${TRAVIS_NODE_VERSION}" = "0.6" ] || [ "${TRAVIS_NODE_VERSION}" = "0.9" ]; then nvm install --latest-npm 0.8 && npm install && nvm use "${TRAVIS_NODE_VERSION}"; else npm install; fi;'
script:
- 'if [ -n "${PRETEST-}" ]; then npm run pretest ; fi'
- 'if [ -n "${TEST-}" ]; then npm run tests-only ; fi'
sudo: false
branches:
only:
- master
env:
- TEST=true
matrix:
fast_finish: true
include:
- node_js: "lts/*"
env: PRETEST=true
allow_failures:
- os: osx
- node_js: "0.12"
- node_js: "0.10"
+99
View File
@@ -0,0 +1,99 @@
2.2.1 / 2019-06-30
==================
- (improvement) Account for TypeCastExpression in the utils
2.2.0 / 2019-06-25
==================
- (fix) Fix getLiteralPropValue for TS-specific node types.
- (chore) upgrade dependencies.
- (improvement) Stop throwing errors when unknown AST nodes are encountered.
- (dev) CI changes.
2.1.0 / 2018-04-19
==================
- Fix undefined bug for template strings. #45
- Adding support for `objectRestSpread` within props #60
- Accommodate ExperimentalSpreadProperty in prop values #75
- Account for SpreadElement AST Nodes #76
- Support OptionalMemberExpression AST nodes #77
- Add support to Typescript's node types #72
2.0.1 / 2017-08-31
==================
- [fix] Add support for BindExpression
2.0.0 / 2017-07-07
==================
- [breaking] Remove undefined return from `propName` so it always returns a value.
1.4.1 / 2017-04-19
==================
- [fix] - Fixing fatal throw in `getPropValue` for `ArrowFunctionExpression`
1.4.0 / 2017-02-02
==================
- [new] Add eventHandlers and eventHandlersByType to API. These are the event names for DOM elements on JSX-using libraries such as React, inferno, and preact.
1.3.5 / 2016-12-14
==================
- [fix] Normalize literals "true" and "false" before converting to boolean in Literal prop value extractor.
1.3.4 / 2016-11-15
==================
- [fix] Recursively resolve JSXMemberExpression names for elementType. (i.e. `<Component.Render.Me />`). Fixes [#9](https://github.com/evcohen/jsx-ast-utils/issues/9)
1.3.3 / 2016-10-28
==================
- [fix] Add support for `ArrayExpression`.
1.3.2 / 2016-10-11
==================
- [fix] Add support for `UpdateExpression`.
1.3.1 / 2016-07-13
==================
- [fix] Add `JSXElement` to expression types to handle recursively extracting prop value.
1.3.0 / 2016-07-12
==================
- [new] Add support for `TaggedTemplateExpression`.
1.2.1 / 2016-06-15
==================
- [fix] Point to `lib` instead of `src` for root exports.
1.2.0 / 2016-06-15
==================
- [new] Export functions from root so they can be imported like the following: `require('jsx-ast-utils/{function}')`.
1.1.1 / 2016-06-12
==================
- [fix] Better support for expressions in `TemplateLiteral` extraction.
1.1.0 / 2016-06-10
==================
- [new] Support for namespaced element names.
- [new] Add `propName` to API to get correct name for prop.
1.0.1 / 2016-06-10
==================
- [fix] Return actual reserved words instead of string representations of them.
1.0.0 / 2016-06-09
==================
- Initial stable release
+8
View File
@@ -0,0 +1,8 @@
The MIT License (MIT)
Copyright (c) 2016 Ethan Cohen
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+294
View File
@@ -0,0 +1,294 @@
<p align="center">
<a href="https://travis-ci.org/evcohen/jsx-ast-utils">
<img src="https://api.travis-ci.org/evcohen/jsx-ast-utils.svg?branch=master"
alt="build status">
</a>
<a href="https://npmjs.org/package/jsx-ast-utils">
<img src="https://img.shields.io/npm/v/jsx-ast-utils.svg"
alt="npm version">
</a>
<a href="https://github.com/evcohen/jsx-ast-utils/blob/master/LICENSE.md">
<img src="https://img.shields.io/npm/l/jsx-ast-utils.svg"
alt="license">
</a>
<a href='https://coveralls.io/github/evcohen/jsx-ast-utils?branch=master'>
<img src='https://coveralls.io/repos/github/evcohen/jsx-ast-utils/badge.svg?branch=master' alt='Coverage Status' />
</a>
<a href='https://npmjs.org/package/jsx-ast-utils'>
<img src='https://img.shields.io/npm/dt/jsx-ast-utils.svg'
alt='Total npm downloads' />
</a>
</p>
# jsx-ast-utils
AST utility module for statically analyzing JSX.
## Installation
```sh
$ npm i jsx-ast-utils --save
```
## Usage
This is a utility module to evaluate AST objects for JSX syntax. This can be super useful when writing linting rules for JSX code. It was originally in the code for [eslint-plugin-jsx-a11y](https://github.com/evcohen/eslint-plugin-jsx-a11y), however I thought it could be useful to be extracted and maintained separately so **you** could write new interesting rules to statically analyze JSX.
### ESLint example
```js
import { hasProp } from 'jsx-ast-utils';
// OR: var hasProp = require('jsx-ast-utils').hasProp;
// OR: const hasProp = require('jsx-ast-utils/hasProp');
// OR: import hasProp from 'jsx-ast-utils/hasProp';
module.exports = context => ({
JSXOpeningElement: node => {
const onChange = hasProp(node.attributes, 'onChange');
if (onChange) {
context.report({
node,
message: `No onChange!`
});
}
}
});
```
## API
### AST Resources
1. [JSX spec](https://github.com/facebook/jsx/blob/master/AST.md)
2. [JS spec](https://github.com/estree/estree/blob/master/spec.md)
### hasProp
```js
hasProp(props, prop, options);
```
Returns boolean indicating whether an prop exists as an attribute on a JSX element node.
#### Props
Object - The attributes on the visited node. (Usually `node.attributes`).
#### Prop
String - A string representation of the prop you want to check for existence.
#### Options
Object - An object representing options for existence checking
1. `ignoreCase` - automatically set to `true`.
2. `spreadStrict` - automatically set to `true`. This means if spread operator exists in
props, it will assume the prop you are looking for is not in the spread.
Example: `<div {...props} />` looking for specific prop here will return false if `spreadStrict` is `true`.
<hr />
### hasAnyProp
```js
hasAnyProp(props, prop, options);
```
Returns a boolean indicating if **any** of props in `prop` argument exist on the node.
#### Props
Object - The attributes on the visited node. (Usually `node.attributes`).
#### Prop
Array<String> - An array of strings representing the props you want to check for existence.
#### Options
Object - An object representing options for existence checking
1. `ignoreCase` - automatically set to `true`.
2. `spreadStrict` - automatically set to `true`. This means if spread operator exists in
props, it will assume the prop you are looking for is not in the spread.
Example: `<div {...props} />` looking for specific prop here will return false if `spreadStrict` is `true`.
<hr />
### hasEveryProp
```js
hasEveryProp(props, prop, options);
```
Returns a boolean indicating if **all** of props in `prop` argument exist on the node.
#### Props
Object - The attributes on the visited node. (Usually `node.attributes`).
#### Prop
Array<String> - An array of strings representing the props you want to check for existence.
#### Options
Object - An object representing options for existence checking
1. `ignoreCase` - automatically set to `true`.
2. `spreadStrict` - automatically set to `true`. This means if spread operator exists in
props, it will assume the prop you are looking for is not in the spread.
Example: `<div {...props} />` looking for specific prop here will return false if `spreadStrict` is `true`.
<hr />
### getProp
```js
getProp(props, prop, options);
```
Returns the JSXAttribute itself or undefined, indicating the prop is not present on the JSXOpeningElement.
#### Props
Object - The attributes on the visited node. (Usually `node.attributes`).
#### Prop
String - A string representation of the prop you want to check for existence.
#### Options
Object - An object representing options for existence checking
1. `ignoreCase` - automatically set to `true`.
<hr />
### elementType
```js
elementType(node)
```
Returns the tagName associated with a JSXElement.
#### Node
Object - The visited JSXElement node object.
<hr />
### getPropValue
```js
getPropValue(prop);
```
Returns the value of a given attribute. Different types of attributes have their associated values in different properties on the object.
This function should return the most *closely* associated value with the intention of the JSX.
#### Prop
Object - The JSXAttribute collected by AST parser.
<hr />
### getLiteralPropValue
```js
getLiteralPropValue(prop);
```
Returns the value of a given attribute. Different types of attributes have their associated values in different properties on the object.
This function should return a value only if we can extract a literal value from its attribute (i.e. values that have generic types in JavaScript - strings, numbers, booleans, etc.)
#### Prop
Object - The JSXAttribute collected by AST parser.
<hr />
### propName
```js
propName(prop);
```
Returns the name associated with a JSXAttribute. For example, given `<div foo="bar" />` and the JSXAttribute for `foo`, this will return the string `"foo"`.
#### Prop
Object - The JSXAttribute collected by AST parser.
<hr />
### eventHandlers
```js
console.log(eventHandlers);
/*
[
'onCopy',
'onCut',
'onPaste',
'onCompositionEnd',
'onCompositionStart',
'onCompositionUpdate',
'onKeyDown',
'onKeyPress',
'onKeyUp',
'onFocus',
'onBlur',
'onChange',
'onInput',
'onSubmit',
'onClick',
'onContextMenu',
'onDblClick',
'onDoubleClick',
'onDrag',
'onDragEnd',
'onDragEnter',
'onDragExit',
'onDragLeave',
'onDragOver',
'onDragStart',
'onDrop',
'onMouseDown',
'onMouseEnter',
'onMouseLeave',
'onMouseMove',
'onMouseOut',
'onMouseOver',
'onMouseUp',
'onSelect',
'onTouchCancel',
'onTouchEnd',
'onTouchMove',
'onTouchStart',
'onScroll',
'onWheel',
'onAbort',
'onCanPlay',
'onCanPlayThrough',
'onDurationChange',
'onEmptied',
'onEncrypted',
'onEnded',
'onError',
'onLoadedData',
'onLoadedMetadata',
'onLoadStart',
'onPause',
'onPlay',
'onPlaying',
'onProgress',
'onRateChange',
'onSeeked',
'onSeeking',
'onStalled',
'onSuspend',
'onTimeUpdate',
'onVolumeChange',
'onWaiting',
'onLoad',
'onError',
'onAnimationStart',
'onAnimationEnd',
'onAnimationIteration',
'onTransitionEnd',
]
*/
```
Contains a flat list of common event handler props used in JSX to attach behaviors
to DOM events.
#### eventHandlersByType
The same list as `eventHandlers`, grouped into types.
```js
console.log(eventHandlersByType);
/*
{
clipboard: [ 'onCopy', 'onCut', 'onPaste' ],
composition: [ 'onCompositionEnd', 'onCompositionStart', 'onCompositionUpdate' ],
keyboard: [ 'onKeyDown', 'onKeyPress', 'onKeyUp' ],
focus: [ 'onFocus', 'onBlur' ],
form: [ 'onChange', 'onInput', 'onSubmit' ],
mouse: [ 'onClick', 'onContextMenu', 'onDblClick', 'onDoubleClick', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 'onDragLeave', 'onDragOver', 'onDragStart', 'onDrop', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 'onMouseMove', 'onMouseOut', 'onMouseOver', 'onMouseUp' ],
selection: [ 'onSelect' ],
touch: [ 'onTouchCancel', 'onTouchEnd', 'onTouchMove', 'onTouchStart' ],
ui: [ 'onScroll' ],
wheel: [ 'onWheel' ],
media: [ 'onAbort', 'onCanPlay', 'onCanPlayThrough', 'onDurationChange', 'onEmptied', 'onEncrypted', 'onEnded', 'onError', 'onLoadedData', 'onLoadedMetadata', 'onLoadStart', 'onPause', 'onPlay', 'onPlaying', 'onProgress', 'onRateChange', 'onSeeked', 'onSeeking', 'onStalled', 'onSuspend', 'onTimeUpdate', 'onVolumeChange', 'onWaiting' ],
image: [ 'onLoad', 'onError' ],
animation: [ 'onAnimationStart', 'onAnimationEnd', 'onAnimationIteration' ],
transition: [ 'onTransitionEnd' ],
}
*/
```
+79
View File
@@ -0,0 +1,79 @@
/* eslint-env jest */
import getProp from '../src/getProp';
const nodeVersion = parseInt(process.version.match(/^v(\d+)\./)[1], 10);
export const fallbackToBabylon = nodeVersion < 6;
let parserName;
const babelParser = fallbackToBabylon ? require('babylon') : require('@babel/parser');
const flowParser = require('flow-parser');
const defaultPlugins = ['jsx', 'functionBind', 'estree', 'objectRestSpread', 'optionalChaining'];
let plugins = [...defaultPlugins];
export function setParserName(name) {
parserName = name;
}
export function changePlugins(pluginOrFn) {
if (Array.isArray(pluginOrFn)) {
plugins = pluginOrFn;
} else if (typeof pluginOrFn === 'function') {
plugins = pluginOrFn(plugins);
} else {
throw new Error('changePlugins argument should be either an array or a function');
}
}
beforeEach(() => {
plugins = [...defaultPlugins];
});
function parse(code) {
if (parserName === undefined) {
throw new Error('No parser specified');
}
if (parserName === 'babel') {
try {
return babelParser.parse(code, { plugins });
} catch (_) {
// eslint-disable-next-line no-console
console.warn(`Failed to parse with ${fallbackToBabylon ? 'babylon' : 'Babel'} parser.`);
}
}
if (parserName === 'flow') {
try {
return flowParser.parse(code, { plugins });
} catch (_) {
// eslint-disable-next-line no-console
console.warn('Failed to parse with the Flow parser');
}
}
throw new Error(`The parser ${parserName} is not yet supported for testing.`);
}
export function getOpeningElement(code) {
const parsedCode = parse(code);
let body;
if (parsedCode.program) {
// eslint-disable-next-line prefer-destructuring
body = parsedCode.program.body;
} else {
// eslint-disable-next-line prefer-destructuring
body = parsedCode.body;
}
if (Array.isArray(body) && body[0] != null) {
return body[0].expression.openingElement;
}
return null;
}
export function extractProp(code, prop = 'foo') {
const node = getOpeningElement(code);
const { attributes: props } = node;
return getProp(props, prop);
}
export const describeIfNotBabylon = fallbackToBabylon ? describe.skip : describe;
+85
View File
@@ -0,0 +1,85 @@
/* eslint-env mocha */
import assert from 'assert';
import { getOpeningElement, setParserName } from '../helper';
import elementType from '../../src/elementType';
describe('elementType tests', () => {
beforeEach(() => {
setParserName('babel');
});
it('should export a function', () => {
const expected = 'function';
const actual = typeof elementType;
assert.equal(expected, actual);
});
it('should throw an error if the argument is missing', () => {
assert.throws(() => { elementType(); }, Error);
});
it('should throw an error if the argument not a JSX node', () => {
assert.throws(() => { elementType({ a: 'foo' }); }, Error);
});
it('should return the correct type of the DOM element given its node object', () => {
const code = '<div />';
const node = getOpeningElement(code);
const expected = 'div';
const actual = elementType(node);
assert.equal(expected, actual);
});
it('should return the correct type of the custom element given its node object', () => {
const code = '<Slider />';
const node = getOpeningElement(code);
const expected = 'Slider';
const actual = elementType(node);
assert.equal(expected, actual);
});
it('should return the correct type of the custom object element given its node object', () => {
const code = '<UX.Slider />';
const node = getOpeningElement(code);
const expected = 'UX.Slider';
const actual = elementType(node);
assert.equal(expected, actual);
});
it('should return the correct type of the namespaced element given its node object', () => {
const code = '<UX:Slider />';
const node = getOpeningElement(code);
const expected = 'UX:Slider';
const actual = elementType(node);
assert.equal(expected, actual);
});
it('should return the correct type of the multiple custom object element given its node object',
() => {
const code = '<UX.Slider.Blue.Light />';
const node = getOpeningElement(code);
const expected = 'UX.Slider.Blue.Light';
const actual = elementType(node);
assert.equal(expected, actual);
});
it('should return this.Component when given its node object', () => {
const code = '<this.Component />';
const node = getOpeningElement(code);
const expected = 'this.Component';
const actual = elementType(node);
assert.equal(expected, actual);
});
});
+101
View File
@@ -0,0 +1,101 @@
/* eslint-env mocha */
import assert from 'assert';
import includes from 'array-includes';
import eventHandlers, { eventHandlersByType } from '../../src/eventHandlers';
describe('eventHandlers', () => {
it('should contain a list of common JSX event handlers', () => {
assert([
'onCopy',
'onCut',
'onPaste',
'onCompositionEnd',
'onCompositionStart',
'onCompositionUpdate',
'onKeyDown',
'onKeyPress',
'onKeyUp',
'onFocus',
'onBlur',
'onChange',
'onInput',
'onSubmit',
'onClick',
'onContextMenu',
'onDblClick',
'onDoubleClick',
'onDrag',
'onDragEnd',
'onDragEnter',
'onDragExit',
'onDragLeave',
'onDragOver',
'onDragStart',
'onDrop',
'onMouseDown',
'onMouseEnter',
'onMouseLeave',
'onMouseMove',
'onMouseOut',
'onMouseOver',
'onMouseUp',
'onSelect',
'onTouchCancel',
'onTouchEnd',
'onTouchMove',
'onTouchStart',
'onScroll',
'onWheel',
'onAbort',
'onCanPlay',
'onCanPlayThrough',
'onDurationChange',
'onEmptied',
'onEncrypted',
'onEnded',
'onError',
'onLoadedData',
'onLoadedMetadata',
'onLoadStart',
'onPause',
'onPlay',
'onPlaying',
'onProgress',
'onRateChange',
'onSeeked',
'onSeeking',
'onStalled',
'onSuspend',
'onTimeUpdate',
'onVolumeChange',
'onWaiting',
'onLoad',
'onError',
'onAnimationStart',
'onAnimationEnd',
'onAnimationIteration',
'onTransitionEnd',
].every(handlerName => includes(eventHandlers, handlerName)));
});
});
describe('eventHandlersByType', () => {
it('should be keyed by type', () => {
assert([
'clipboard',
'composition',
'keyboard',
'focus',
'form',
'mouse',
'selection',
'touch',
'ui',
'wheel',
'media',
'image',
'animation',
'transition',
].every(type => !!eventHandlersByType[type]));
});
});
+74
View File
@@ -0,0 +1,74 @@
/* eslint-env mocha */
import assert from 'assert';
import { getOpeningElement, setParserName } from '../helper';
import getProp from '../../src/getProp';
describe('getProp', () => {
beforeEach(() => {
setParserName('babel');
});
it('should export a function', () => {
const expected = 'function';
const actual = typeof getProp;
assert.equal(expected, actual);
});
it('should return undefined if no arguments are provided', () => {
const expected = undefined;
const actual = getProp();
assert.equal(expected, actual);
});
it('should return undefined if the attribute is absent', () => {
const code = '<div />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const expected = undefined;
const actual = getProp(props, prop);
assert.equal(expected, actual);
});
it('should return the correct attribute if the attribute exists', () => {
const code = '<div id="foo" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const expected = 'id';
const actual = getProp(props, prop).name.name;
assert.equal(expected, actual);
});
it('should return undefined if the attribute may exist in spread', () => {
const code = '<div {...props} />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const expected = undefined;
const actual = getProp(props, prop);
assert.equal(expected, actual);
});
it('should return undefined if the attribute is considered absent in case-sensitive mode', () => {
const code = '<div ID="foo" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const options = {
ignoreCase: false,
};
const expected = undefined;
const actual = getProp(props, prop, options);
assert.equal(expected, actual);
});
});
@@ -0,0 +1,522 @@
/* eslint-env mocha */
/* eslint no-template-curly-in-string: 0 */
import assert from 'assert';
import {
extractProp,
describeIfNotBabylon,
changePlugins,
setParserName,
} from '../helper';
import { getLiteralPropValue } from '../../src/getPropValue';
describe('getLiteralPropValue', () => {
beforeEach(() => {
setParserName('babel');
});
it('should export a function', () => {
const expected = 'function';
const actual = typeof getLiteralPropValue;
assert.equal(expected, actual);
});
it('should return undefined when not provided with a JSXAttribute', () => {
const expected = undefined;
const actual = getLiteralPropValue(1);
assert.equal(expected, actual);
});
it('should not throw error when trying to get value from unknown node type', () => {
const prop = {
type: 'JSXAttribute',
value: {
type: 'JSXExpressionContainer',
},
};
let counter = 0;
// eslint-disable-next-line no-console
const errorOrig = console.error;
// eslint-disable-next-line no-console
console.error = () => {
counter += 1;
};
let value;
assert.doesNotThrow(() => {
value = getLiteralPropValue(prop);
}, Error);
assert.equal(null, value);
assert.equal(counter, 1);
// eslint-disable-next-line no-console
console.error = errorOrig;
});
describe('Null', () => {
it('should return true when no value is given', () => {
const prop = extractProp('<div foo />');
const expected = true;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Literal', () => {
it('should return correct string if value is a string', () => {
const prop = extractProp('<div foo="bar" />');
const expected = 'bar';
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return correct string if value is a string expression', () => {
const prop = extractProp('<div foo={"bar"} />');
const expected = 'bar';
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return correct integer if value is a integer expression', () => {
const prop = extractProp('<div foo={1} />');
const expected = 1;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should convert "true" to boolean type', () => {
const prop = extractProp('<div foo="true" />');
const expected = true;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should convert "TrUE" to boolean type', () => {
const prop = extractProp('<div foo="TrUE" />');
const expected = true;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should convert "false" to boolean type', () => {
const prop = extractProp('<div foo="false" />');
const expected = false;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should convert "FaLsE" to boolean type', () => {
const prop = extractProp('<div foo="FaLsE" />');
const expected = false;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return String null when value is null', () => {
const prop = extractProp('<div foo={null} />');
const expected = 'null';
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('JSXElement', () => {
it('should return null', () => {
const prop = extractProp('<div foo={<bar />} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Identifier', () => {
it('should return null', () => {
const prop = extractProp('<div foo={bar} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return undefined when identifier is literally `undefined`', () => {
const prop = extractProp('<div foo={undefined} />');
const expected = undefined;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Template literal', () => {
it('should return template literal with vars wrapped in curly braces', () => {
const prop = extractProp('<div foo={`bar ${baz}`} />');
const expected = 'bar {baz}';
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return string "undefined" for expressions that evaluate to undefined', () => {
const prop = extractProp('<div foo={`bar ${undefined}`} />');
const expected = 'bar undefined';
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Tagged Template literal', () => {
it('should return template literal with vars wrapped in curly braces', () => {
const prop = extractProp('<div foo={noop`bar ${baz}`} />');
const expected = 'bar {baz}';
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return string "undefined" for expressions that evaluate to undefined', () => {
const prop = extractProp('<div foo={noop`bar ${undefined}`} />');
const expected = 'bar undefined';
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Arrow function expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={ () => { return "bar"; }} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Function expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={ function() { return "bar"; } } />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Logical expression', () => {
it('should return null for && operator', () => {
const prop = extractProp('<div foo={bar && baz} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return null for || operator', () => {
const prop = extractProp('<div foo={bar || baz} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Member expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={bar.baz} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Call expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={bar()} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Unary expression', () => {
it('should correctly evaluate an expression that prefixes with -', () => {
const prop = extractProp('<div foo={-bar} />');
// -"bar" => NaN
const expected = true;
const actual = Number.isNaN(getLiteralPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with -', () => {
const prop = extractProp('<div foo={-42} />');
const expected = -42;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with +', () => {
const prop = extractProp('<div foo={+bar} />');
// +"bar" => NaN
const expected = true;
const actual = Number.isNaN(getLiteralPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with +', () => {
const prop = extractProp('<div foo={+42} />');
const expected = 42;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with !', () => {
const prop = extractProp('<div foo={!bar} />');
const expected = false; // !"bar" === false
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with ~', () => {
const prop = extractProp('<div foo={~bar} />');
const expected = -1; // ~"bar" === -1
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return true when evaluating `delete foo`', () => {
const prop = extractProp('<div foo={delete x} />');
const expected = true;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return undefined when evaluating `void foo`', () => {
const prop = extractProp('<div foo={void x} />');
const expected = undefined;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
// TODO: We should fix this to check to see if we can evaluate it.
it('should return undefined when evaluating `typeof foo`', () => {
const prop = extractProp('<div foo={typeof x} />');
const expected = undefined;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Update expression', () => {
it('should correctly evaluate an expression that prefixes with ++', () => {
const prop = extractProp('<div foo={++bar} />');
// ++"bar" => NaN
const expected = true;
const actual = Number.isNaN(getLiteralPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with --', () => {
const prop = extractProp('<div foo={--bar} />');
// --"bar" => NaN
const expected = true;
const actual = Number.isNaN(getLiteralPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that suffixes with ++', () => {
const prop = extractProp('<div foo={bar++} />');
// "bar"++ => NaN
const expected = true;
const actual = Number.isNaN(getLiteralPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that suffixes with --', () => {
const prop = extractProp('<div foo={bar--} />');
// "bar"-- => NaN
const expected = true;
const actual = Number.isNaN(getLiteralPropValue(prop));
assert.equal(expected, actual);
});
});
describe('This expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={this} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Conditional expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={bar ? baz : bam} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Binary expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={1 == "1"} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Object expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={ { bar: "baz" } } />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.deepEqual(expected, actual);
});
});
describe('New expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={new Bar()} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.deepEqual(expected, actual);
});
});
describe('Array expression', () => {
it('should evaluate to correct representation of the the array in props', () => {
const prop = extractProp('<div foo={["bar", 42, null]} />');
const expected = ['bar', 42];
const actual = getLiteralPropValue(prop);
assert.deepEqual(expected, actual);
});
});
it('should return an empty array provided an empty array in props', () => {
const prop = extractProp('<div foo={[]} />');
const expected = [];
const actual = getLiteralPropValue(prop);
assert.deepEqual(expected, actual);
});
describe('Bind expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={::this.handleClick} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.deepEqual(expected, actual);
});
});
describeIfNotBabylon('Typescript', () => {
beforeEach(() => {
changePlugins(pls => [...pls, 'typescript']);
});
it('should return string representation of variable identifier wrapped in a Typescript non-null assertion', () => {
const prop = extractProp('<div foo={bar!} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return string representation of variable identifier wrapped in a deep Typescript non-null assertion', () => {
const prop = extractProp('<div foo={(bar!)!} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return string representation of variable identifier wrapped in a Typescript type coercion', () => {
changePlugins(pls => [...pls, 'typescript']);
const prop = extractProp('<div foo={bar as any} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
});
@@ -0,0 +1,522 @@
/* eslint-env mocha */
/* eslint no-template-curly-in-string: 0 */
import assert from 'assert';
import {
extractProp,
describeIfNotBabylon,
changePlugins,
setParserName,
} from '../helper';
import { getLiteralPropValue } from '../../src/getPropValue';
describe('getLiteralPropValue', () => {
beforeEach(() => {
setParserName('flow');
});
it('should export a function', () => {
const expected = 'function';
const actual = typeof getLiteralPropValue;
assert.equal(expected, actual);
});
it('should return undefined when not provided with a JSXAttribute', () => {
const expected = undefined;
const actual = getLiteralPropValue(1);
assert.equal(expected, actual);
});
it('should not throw error when trying to get value from unknown node type', () => {
const prop = {
type: 'JSXAttribute',
value: {
type: 'JSXExpressionContainer',
},
};
let counter = 0;
// eslint-disable-next-line no-console
const errorOrig = console.error;
// eslint-disable-next-line no-console
console.error = () => {
counter += 1;
};
let value;
assert.doesNotThrow(() => {
value = getLiteralPropValue(prop);
}, Error);
assert.equal(null, value);
assert.equal(counter, 1);
// eslint-disable-next-line no-console
console.error = errorOrig;
});
describe('Null', () => {
it('should return true when no value is given', () => {
const prop = extractProp('<div foo />');
const expected = true;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Literal', () => {
it('should return correct string if value is a string', () => {
const prop = extractProp('<div foo="bar" />');
const expected = 'bar';
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return correct string if value is a string expression', () => {
const prop = extractProp('<div foo={"bar"} />');
const expected = 'bar';
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return correct integer if value is a integer expression', () => {
const prop = extractProp('<div foo={1} />');
const expected = 1;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should convert "true" to boolean type', () => {
const prop = extractProp('<div foo="true" />');
const expected = true;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should convert "TrUE" to boolean type', () => {
const prop = extractProp('<div foo="TrUE" />');
const expected = true;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should convert "false" to boolean type', () => {
const prop = extractProp('<div foo="false" />');
const expected = false;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should convert "FaLsE" to boolean type', () => {
const prop = extractProp('<div foo="FaLsE" />');
const expected = false;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return String null when value is null', () => {
const prop = extractProp('<div foo={null} />');
const expected = 'null';
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('JSXElement', () => {
it('should return null', () => {
const prop = extractProp('<div foo={<bar />} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Identifier', () => {
it('should return null', () => {
const prop = extractProp('<div foo={bar} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return undefined when identifier is literally `undefined`', () => {
const prop = extractProp('<div foo={undefined} />');
const expected = undefined;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Template literal', () => {
it('should return template literal with vars wrapped in curly braces', () => {
const prop = extractProp('<div foo={`bar ${baz}`} />');
const expected = 'bar {baz}';
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return string "undefined" for expressions that evaluate to undefined', () => {
const prop = extractProp('<div foo={`bar ${undefined}`} />');
const expected = 'bar undefined';
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Tagged Template literal', () => {
it('should return template literal with vars wrapped in curly braces', () => {
const prop = extractProp('<div foo={noop`bar ${baz}`} />');
const expected = 'bar {baz}';
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return string "undefined" for expressions that evaluate to undefined', () => {
const prop = extractProp('<div foo={noop`bar ${undefined}`} />');
const expected = 'bar undefined';
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Arrow function expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={ () => { return "bar"; }} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Function expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={ function() { return "bar"; } } />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Logical expression', () => {
it('should return null for && operator', () => {
const prop = extractProp('<div foo={bar && baz} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return null for || operator', () => {
const prop = extractProp('<div foo={bar || baz} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Member expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={bar.baz} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Call expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={bar()} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Unary expression', () => {
it('should correctly evaluate an expression that prefixes with -', () => {
const prop = extractProp('<div foo={-bar} />');
// -"bar" => NaN
const expected = true;
const actual = Number.isNaN(getLiteralPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with -', () => {
const prop = extractProp('<div foo={-42} />');
const expected = -42;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with +', () => {
const prop = extractProp('<div foo={+bar} />');
// +"bar" => NaN
const expected = true;
const actual = Number.isNaN(getLiteralPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with +', () => {
const prop = extractProp('<div foo={+42} />');
const expected = 42;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with !', () => {
const prop = extractProp('<div foo={!bar} />');
const expected = false; // !"bar" === false
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with ~', () => {
const prop = extractProp('<div foo={~bar} />');
const expected = -1; // ~"bar" === -1
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return true when evaluating `delete foo`', () => {
const prop = extractProp('<div foo={delete x} />');
const expected = true;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return undefined when evaluating `void foo`', () => {
const prop = extractProp('<div foo={void x} />');
const expected = undefined;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
// TODO: We should fix this to check to see if we can evaluate it.
it('should return undefined when evaluating `typeof foo`', () => {
const prop = extractProp('<div foo={typeof x} />');
const expected = undefined;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Update expression', () => {
it('should correctly evaluate an expression that prefixes with ++', () => {
const prop = extractProp('<div foo={++bar} />');
// ++"bar" => NaN
const expected = true;
const actual = Number.isNaN(getLiteralPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with --', () => {
const prop = extractProp('<div foo={--bar} />');
// --"bar" => NaN
const expected = true;
const actual = Number.isNaN(getLiteralPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that suffixes with ++', () => {
const prop = extractProp('<div foo={bar++} />');
// "bar"++ => NaN
const expected = true;
const actual = Number.isNaN(getLiteralPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that suffixes with --', () => {
const prop = extractProp('<div foo={bar--} />');
// "bar"-- => NaN
const expected = true;
const actual = Number.isNaN(getLiteralPropValue(prop));
assert.equal(expected, actual);
});
});
describe('This expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={this} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Conditional expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={bar ? baz : bam} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Binary expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={1 == "1"} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Object expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={ { bar: "baz" } } />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.deepEqual(expected, actual);
});
});
describe('New expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={new Bar()} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.deepEqual(expected, actual);
});
});
describe('Array expression', () => {
it('should evaluate to correct representation of the the array in props', () => {
const prop = extractProp('<div foo={["bar", 42, null]} />');
const expected = ['bar', 42];
const actual = getLiteralPropValue(prop);
assert.deepEqual(expected, actual);
});
});
it('should return an empty array provided an empty array in props', () => {
const prop = extractProp('<div foo={[]} />');
const expected = [];
const actual = getLiteralPropValue(prop);
assert.deepEqual(expected, actual);
});
describe('Bind expression', () => {
it('should return null', () => {
const prop = extractProp('<div foo={::this.handleClick} />');
const expected = 'null';
const actual = getLiteralPropValue(prop);
assert.deepEqual(expected, actual);
});
});
describeIfNotBabylon('Typescript', () => {
beforeEach(() => {
changePlugins(pls => [...pls, 'typescript']);
});
it('should return string representation of variable identifier wrapped in a Typescript non-null assertion', () => {
const prop = extractProp('<div foo={bar!} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return string representation of variable identifier wrapped in a deep Typescript non-null assertion', () => {
const prop = extractProp('<div foo={(bar!)!} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
it('should return string representation of variable identifier wrapped in a Typescript type coercion', () => {
changePlugins(pls => [...pls, 'typescript']);
const prop = extractProp('<div foo={bar as any} />');
const expected = null;
const actual = getLiteralPropValue(prop);
assert.equal(expected, actual);
});
});
});
@@ -0,0 +1,956 @@
/* eslint-env mocha */
/* eslint no-template-curly-in-string: 0 */
import assert from 'assert';
import {
extractProp,
changePlugins,
fallbackToBabylon,
describeIfNotBabylon,
setParserName,
} from '../helper';
import getPropValue from '../../src/getPropValue';
describe('getPropValue', () => {
beforeEach(() => {
setParserName('babel');
});
it('should export a function', () => {
const expected = 'function';
const actual = typeof getPropValue;
assert.equal(expected, actual);
});
it('should return undefined when not provided with a JSXAttribute', () => {
const expected = undefined;
const actual = getPropValue(1);
assert.equal(expected, actual);
});
it('should not throw error when trying to get value from unknown node type', () => {
const prop = {
type: 'JSXAttribute',
value: {
type: 'JSXExpressionContainer',
},
};
let counter = 0;
// eslint-disable-next-line no-console
const errorOrig = console.error;
// eslint-disable-next-line no-console
console.error = () => {
counter += 1;
};
let value;
assert.doesNotThrow(() => {
value = getPropValue(prop);
}, Error);
assert.equal(null, value);
assert.equal(counter, 1);
// eslint-disable-next-line no-console
console.error = errorOrig;
});
describe('Null', () => {
it('should return true when no value is given', () => {
const prop = extractProp('<div foo />');
const expected = true;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Literal', () => {
it('should return correct string if value is a string', () => {
const prop = extractProp('<div foo="bar" />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return correct string if value is a string expression', () => {
const prop = extractProp('<div foo={"bar"} />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return correct integer if value is a integer expression', () => {
const prop = extractProp('<div foo={1} />');
const expected = 1;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should convert "true" to boolean type', () => {
const prop = extractProp('<div foo="true" />');
const expected = true;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should convert "false" to boolean type', () => {
const prop = extractProp('<div foo="false" />');
const expected = false;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('JSXElement', () => {
it('should return correct representation of JSX element as a string', () => {
const prop = extractProp('<div foo={<bar />} />');
const expected = '<bar />';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Identifier', () => {
it('should return string representation of variable identifier', () => {
const prop = extractProp('<div foo={bar} />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return undefined when identifier is literally `undefined`', () => {
const prop = extractProp('<div foo={undefined} />');
const expected = undefined;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return String object when using a reserved JavaScript object', () => {
const prop = extractProp('<div foo={String} />');
const expected = String;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return Array object when using a reserved JavaScript object', () => {
const prop = extractProp('<div foo={Array} />');
const expected = Array;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return Date object when using a reserved JavaScript object', () => {
const prop = extractProp('<div foo={Date} />');
const expected = Date;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return Infinity object when using a reserved JavaScript object', () => {
const prop = extractProp('<div foo={Infinity} />');
const expected = Infinity;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return Math object when using a reserved JavaScript object', () => {
const prop = extractProp('<div foo={Math} />');
const expected = Math;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return Number object when using a reserved JavaScript object', () => {
const prop = extractProp('<div foo={Number} />');
const expected = Number;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return Object object when using a reserved JavaScript object', () => {
const prop = extractProp('<div foo={Object} />');
const expected = Object;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Template literal', () => {
it('should return template literal with vars wrapped in curly braces', () => {
const prop = extractProp('<div foo={`bar ${baz}`} />');
const expected = 'bar {baz}';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return string "undefined" for expressions that evaluate to undefined', () => {
const prop = extractProp('<div foo={`bar ${undefined}`} />');
const expected = 'bar undefined';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return template literal with expression type wrapped in curly braces', () => {
const prop = extractProp('<div foo={`bar ${baz()}`} />');
const expected = 'bar {CallExpression}';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should ignore non-expressions in the template literal', () => {
const prop = extractProp('<div foo={`bar ${<baz />}`} />');
const expected = 'bar ';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Tagged Template literal', () => {
it('should return template literal with vars wrapped in curly braces', () => {
const prop = extractProp('<div foo={noop`bar ${baz}`} />');
const expected = 'bar {baz}';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return string "undefined" for expressions that evaluate to undefined', () => {
const prop = extractProp('<div foo={noop`bar ${undefined}`} />');
const expected = 'bar undefined';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return template literal with expression type wrapped in curly braces', () => {
const prop = extractProp('<div foo={noop`bar ${baz()}`} />');
const expected = 'bar {CallExpression}';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should ignore non-expressions in the template literal', () => {
const prop = extractProp('<div foo={noop`bar ${<baz />}`} />');
const expected = 'bar ';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Arrow function expression', () => {
it('should return a function', () => {
const prop = extractProp('<div foo={ () => { return "bar"; }} />');
const expected = 'function';
const actual = getPropValue(prop);
assert.equal(expected, typeof actual);
// For code coverage ¯\_(ツ)_/¯
actual();
});
it('should handle ArrowFunctionExpression as conditional consequent', () => {
const prop = extractProp('<div foo={ (true) ? () => null : () => ({})} />');
const expected = 'function';
const actual = getPropValue(prop);
assert.equal(expected, typeof actual);
// For code coverage ¯\_(ツ)_/¯
actual();
});
});
describe('Function expression', () => {
it('should return a function', () => {
const prop = extractProp('<div foo={ function() { return "bar"; } } />');
const expected = 'function';
const actual = getPropValue(prop);
assert.equal(expected, typeof actual);
// For code coverage ¯\_(ツ)_/¯
actual();
});
});
describe('Logical expression', () => {
it('should correctly infer result of && logical expression based on derived values', () => {
const prop = extractProp('<div foo={bar && baz} />');
const expected = 'baz';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return undefined when evaluating `undefined && undefined` ', () => {
const prop = extractProp('<div foo={undefined && undefined} />');
const expected = undefined;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly infer result of || logical expression based on derived values', () => {
const prop = extractProp('<div foo={bar || baz} />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly infer result of || logical expression based on derived values', () => {
const prop = extractProp('<div foo={undefined || baz} />');
const expected = 'baz';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return undefined when evaluating `undefined || undefined` ', () => {
const prop = extractProp('<div foo={undefined || undefined} />');
const expected = undefined;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Member expression', () => {
it('should return string representation of form `object.property`', () => {
const prop = extractProp('<div foo={bar.baz} />');
const expected = 'bar.baz';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate to a correct representation of member expression with a nullable member', () => {
const runTest = () => {
const prop = extractProp('<div foo={bar?.baz} />');
const expected = 'bar?.baz';
const actual = getPropValue(prop);
assert.equal(expected, actual);
};
if (fallbackToBabylon) {
// eslint-disable-next-line no-undef
expect(runTest).toThrow();
} else {
runTest();
}
});
});
describe('Call expression', () => {
it('should return string representation of callee', () => {
const prop = extractProp('<div foo={bar()} />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return string representation of callee', () => {
const prop = extractProp('<div foo={bar.call()} />');
const expected = 'bar.call';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Unary expression', () => {
it('should correctly evaluate an expression that prefixes with -', () => {
const prop = extractProp('<div foo={-bar} />');
// -"bar" => NaN
const expected = true;
const actual = Number.isNaN(getPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with -', () => {
const prop = extractProp('<div foo={-42} />');
const expected = -42;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with +', () => {
const prop = extractProp('<div foo={+bar} />');
// +"bar" => NaN
const expected = true;
const actual = Number.isNaN(getPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with +', () => {
const prop = extractProp('<div foo={+42} />');
const expected = 42;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with !', () => {
const prop = extractProp('<div foo={!bar} />');
const expected = false; // !"bar" === false
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with ~', () => {
const prop = extractProp('<div foo={~bar} />');
const expected = -1; // ~"bar" === -1
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return true when evaluating `delete foo`', () => {
const prop = extractProp('<div foo={delete x} />');
const expected = true;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return undefined when evaluating `void foo`', () => {
const prop = extractProp('<div foo={void x} />');
const expected = undefined;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
// TODO: We should fix this to check to see if we can evaluate it.
it('should return undefined when evaluating `typeof foo`', () => {
const prop = extractProp('<div foo={typeof x} />');
const expected = undefined;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Update expression', () => {
it('should correctly evaluate an expression that prefixes with ++', () => {
const prop = extractProp('<div foo={++bar} />');
// ++"bar" => NaN
const expected = true;
const actual = Number.isNaN(getPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with --', () => {
const prop = extractProp('<div foo={--bar} />');
const expected = true;
const actual = Number.isNaN(getPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that suffixes with ++', () => {
const prop = extractProp('<div foo={bar++} />');
// "bar"++ => NaN
const expected = true;
const actual = Number.isNaN(getPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that suffixes with --', () => {
const prop = extractProp('<div foo={bar--} />');
const expected = true;
const actual = Number.isNaN(getPropValue(prop));
assert.equal(expected, actual);
});
});
describe('This expression', () => {
it('should return string value `this`', () => {
const prop = extractProp('<div foo={this} />');
const expected = 'this';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Conditional expression', () => {
it('should evaluate the conditional based on the derived values correctly', () => {
const prop = extractProp('<div foo={bar ? baz : bam} />');
const expected = 'baz';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the conditional based on the derived values correctly', () => {
const prop = extractProp('<div foo={undefined ? baz : bam} />');
const expected = 'bam';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the conditional based on the derived values correctly', () => {
const prop = extractProp('<div foo={(1 > 2) ? baz : bam} />');
const expected = 'bam';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Binary expression', () => {
it('should evaluate the `==` operator correctly', () => {
const trueProp = extractProp('<div foo={1 == "1"} />');
const falseProp = extractProp('<div foo={1 == bar} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `!=` operator correctly', () => {
const trueProp = extractProp('<div foo={1 != "2"} />');
const falseProp = extractProp('<div foo={1 != "1"} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `===` operator correctly', () => {
const trueProp = extractProp('<div foo={1 === 1} />');
const falseProp = extractProp('<div foo={1 === "1"} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `!==` operator correctly', () => {
const trueProp = extractProp('<div foo={1 !== "1"} />');
const falseProp = extractProp('<div foo={1 !== 1} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `<` operator correctly', () => {
const trueProp = extractProp('<div foo={1 < 2} />');
const falseProp = extractProp('<div foo={1 < 0} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `>` operator correctly', () => {
const trueProp = extractProp('<div foo={1 > 0} />');
const falseProp = extractProp('<div foo={1 > 2} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `<=` operator correctly', () => {
const trueProp = extractProp('<div foo={1 <= 1} />');
const falseProp = extractProp('<div foo={1 <= 0} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `>=` operator correctly', () => {
const trueProp = extractProp('<div foo={1 >= 1} />');
const falseProp = extractProp('<div foo={1 >= 2} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `<<` operator correctly', () => {
const prop = extractProp('<div foo={1 << 2} />');
const expected = 4;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `>>` operator correctly', () => {
const prop = extractProp('<div foo={1 >> 2} />');
const expected = 0;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `>>>` operator correctly', () => {
const prop = extractProp('<div foo={2 >>> 1} />');
const expected = 1;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `+` operator correctly', () => {
const prop = extractProp('<div foo={1 + 1} />');
const expected = 2;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `-` operator correctly', () => {
const prop = extractProp('<div foo={1 - 1} />');
const expected = 0;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `*` operator correctly', () => {
const prop = extractProp('<div foo={10 * 10} />');
const expected = 100;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `/` operator correctly', () => {
const prop = extractProp('<div foo={10 / 2} />');
const expected = 5;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `%` operator correctly', () => {
const prop = extractProp('<div foo={10 % 3} />');
const expected = 1;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `|` operator correctly', () => {
const prop = extractProp('<div foo={10 | 1} />');
const expected = 11;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `^` operator correctly', () => {
const prop = extractProp('<div foo={10 ^ 1} />');
const expected = 11;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `&` operator correctly', () => {
const prop = extractProp('<div foo={10 & 1} />');
const expected = 0;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `in` operator correctly', () => {
const prop = extractProp('<div foo={foo in bar} />');
const expected = false;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `instanceof` operator correctly', () => {
const prop = extractProp('<div foo={{} instanceof Object} />');
const expected = true;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `instanceof` operator when right side is not a function', () => {
const prop = extractProp('<div foo={"bar" instanceof Baz} />');
const expected = false;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Object expression', () => {
it('should evaluate to a correct representation of the object in props', () => {
const prop = extractProp('<div foo={ { bar: "baz" } } />');
const expected = { bar: 'baz' };
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
it('should evaluate to a correct representation of the object, ignore spread properties', () => {
const prop = extractProp('<div foo={{bar: "baz", ...{baz: "bar", foo: {...{bar: "meh"}}}}} />');
const expected = { bar: 'baz', baz: 'bar', foo: { bar: 'meh' } };
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
it('should evaluate to a correct representation of the object, ignore spread properties', () => {
const prop = extractProp('<div foo={{ pathname: manageRoute, state: {...data}}} />');
const expected = { pathname: 'manageRoute', state: {} };
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
});
describe('New expression', () => {
it('should return a new empty object', () => {
const prop = extractProp('<div foo={new Bar()} />');
const expected = {};
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
});
describe('Array expression', () => {
it('should evaluate to correct representation of the the array in props', () => {
const prop = extractProp('<div foo={["bar", 42, null]} />');
const expected = ['bar', 42, null];
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
it('should evaluate to a correct representation of an array with spread elements', () => {
const prop = extractProp('<div foo={[...this.props.params, bar]} />');
const expected = [undefined, 'bar'];
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
});
it('should return an empty array provided an empty array in props', () => {
const prop = extractProp('<div foo={[]} />');
const expected = [];
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
describe('Bind expression', () => {
it('should return string representation of bind function call when object is null', () => {
const prop = extractProp('<div foo={::this.handleClick} />');
const expected = 'this.handleClick.bind(this)';
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
it('should return string representation of bind function call when object is not null', () => {
const prop = extractProp('<div foo={foo::bar} />');
const expected = 'bar.bind(foo)';
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
it('should return string representation of bind function call when binding to object properties', () => {
const prop = extractProp('<div foo={a.b::c} />');
const otherProp = extractProp('<div foo={::a.b.c} />');
const expected = 'a.b.c.bind(a.b)';
const actual = getPropValue(prop);
const otherExpected = 'a.b.c.bind(a.b)';
const otherActual = getPropValue(otherProp);
assert.deepEqual(expected, actual);
assert.deepEqual(otherExpected, otherActual);
});
});
describe('Type Cast Expression', () => {
it('should throw a parsing error', () => {
let counter = 0;
// eslint-disable-next-line no-console
const warnOrig = console.warn;
// eslint-disable-next-line no-console
console.warn = () => {
counter += 1;
};
// eslint-disable-next-line no-undef
expect(() => {
extractProp('<div foo={(this.handleClick: (event: MouseEvent) => void))} />');
}).toThrow();
assert.equal(counter, 1);
// eslint-disable-next-line no-console
console.warn = warnOrig;
});
});
describeIfNotBabylon('Typescript', () => {
beforeEach(() => {
changePlugins(pls => [...pls, 'typescript']);
});
it('should return string representation of variable identifier wrapped in a Typescript non-null assertion', () => {
const prop = extractProp('<div foo={bar!} />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return string representation of variable identifier wrapped in a deep Typescript non-null assertion', () => {
const prop = extractProp('<div foo={(bar!)!} />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return string representation of variable identifier wrapped in a Typescript type coercion', () => {
changePlugins(pls => [...pls, 'typescript']);
const prop = extractProp('<div foo={bar as any} />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
});
@@ -0,0 +1,938 @@
/* eslint-env mocha */
/* eslint no-template-curly-in-string: 0 */
import assert from 'assert';
import {
extractProp,
changePlugins,
describeIfNotBabylon,
setParserName,
} from '../helper';
import getPropValue from '../../src/getPropValue';
describe('getPropValue', () => {
beforeEach(() => {
setParserName('flow');
});
it('should export a function', () => {
const expected = 'function';
const actual = typeof getPropValue;
assert.equal(expected, actual);
});
it('should return undefined when not provided with a JSXAttribute', () => {
const expected = undefined;
const actual = getPropValue(1);
assert.equal(expected, actual);
});
it('should throw not error when trying to get value from unknown node type', () => {
const prop = {
type: 'JSXAttribute',
value: {
type: 'JSXExpressionContainer',
},
};
let counter = 0;
// eslint-disable-next-line no-console
const errorOrig = console.error;
// eslint-disable-next-line no-console
console.error = () => {
counter += 1;
};
let value;
assert.doesNotThrow(() => {
value = getPropValue(prop);
}, Error);
assert.equal(null, value);
assert.equal(counter, 1);
// eslint-disable-next-line no-console
console.error = errorOrig;
});
describe('Null', () => {
it('should return true when no value is given', () => {
const prop = extractProp('<div foo />');
const expected = true;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Literal', () => {
it('should return correct string if value is a string', () => {
const prop = extractProp('<div foo="bar" />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return correct string if value is a string expression', () => {
const prop = extractProp('<div foo={"bar"} />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return correct integer if value is a integer expression', () => {
const prop = extractProp('<div foo={1} />');
const expected = 1;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should convert "true" to boolean type', () => {
const prop = extractProp('<div foo="true" />');
const expected = true;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should convert "false" to boolean type', () => {
const prop = extractProp('<div foo="false" />');
const expected = false;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('JSXElement', () => {
it('should return correct representation of JSX element as a string', () => {
const prop = extractProp('<div foo={<bar />} />');
const expected = '<bar />';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Identifier', () => {
it('should return string representation of variable identifier', () => {
const prop = extractProp('<div foo={bar} />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return undefined when identifier is literally `undefined`', () => {
const prop = extractProp('<div foo={undefined} />');
const expected = undefined;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return String object when using a reserved JavaScript object', () => {
const prop = extractProp('<div foo={String} />');
const expected = String;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return Array object when using a reserved JavaScript object', () => {
const prop = extractProp('<div foo={Array} />');
const expected = Array;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return Date object when using a reserved JavaScript object', () => {
const prop = extractProp('<div foo={Date} />');
const expected = Date;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return Infinity object when using a reserved JavaScript object', () => {
const prop = extractProp('<div foo={Infinity} />');
const expected = Infinity;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return Math object when using a reserved JavaScript object', () => {
const prop = extractProp('<div foo={Math} />');
const expected = Math;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return Number object when using a reserved JavaScript object', () => {
const prop = extractProp('<div foo={Number} />');
const expected = Number;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return Object object when using a reserved JavaScript object', () => {
const prop = extractProp('<div foo={Object} />');
const expected = Object;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Template literal', () => {
it('should return template literal with vars wrapped in curly braces', () => {
const prop = extractProp('<div foo={`bar ${baz}`} />');
const expected = 'bar {baz}';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return string "undefined" for expressions that evaluate to undefined', () => {
const prop = extractProp('<div foo={`bar ${undefined}`} />');
const expected = 'bar undefined';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return template literal with expression type wrapped in curly braces', () => {
const prop = extractProp('<div foo={`bar ${baz()}`} />');
const expected = 'bar {CallExpression}';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should ignore non-expressions in the template literal', () => {
const prop = extractProp('<div foo={`bar ${<baz />}`} />');
const expected = 'bar ';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Tagged Template literal', () => {
it('should return template literal with vars wrapped in curly braces', () => {
const prop = extractProp('<div foo={noop`bar ${baz}`} />');
const expected = 'bar {baz}';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return string "undefined" for expressions that evaluate to undefined', () => {
const prop = extractProp('<div foo={noop`bar ${undefined}`} />');
const expected = 'bar undefined';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return template literal with expression type wrapped in curly braces', () => {
const prop = extractProp('<div foo={noop`bar ${baz()}`} />');
const expected = 'bar {CallExpression}';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should ignore non-expressions in the template literal', () => {
const prop = extractProp('<div foo={noop`bar ${<baz />}`} />');
const expected = 'bar ';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Arrow function expression', () => {
it('should return a function', () => {
const prop = extractProp('<div foo={ () => { return "bar"; }} />');
const expected = 'function';
const actual = getPropValue(prop);
assert.equal(expected, typeof actual);
// For code coverage ¯\_(ツ)_/¯
actual();
});
it('should handle ArrowFunctionExpression as conditional consequent', () => {
const prop = extractProp('<div foo={ (true) ? () => null : () => ({})} />');
const expected = 'function';
const actual = getPropValue(prop);
assert.equal(expected, typeof actual);
// For code coverage ¯\_(ツ)_/¯
actual();
});
});
describe('Function expression', () => {
it('should return a function', () => {
const prop = extractProp('<div foo={ function() { return "bar"; } } />');
const expected = 'function';
const actual = getPropValue(prop);
assert.equal(expected, typeof actual);
// For code coverage ¯\_(ツ)_/¯
actual();
});
});
describe('Logical expression', () => {
it('should correctly infer result of && logical expression based on derived values', () => {
const prop = extractProp('<div foo={bar && baz} />');
const expected = 'baz';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return undefined when evaluating `undefined && undefined` ', () => {
const prop = extractProp('<div foo={undefined && undefined} />');
const expected = undefined;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly infer result of || logical expression based on derived values', () => {
const prop = extractProp('<div foo={bar || baz} />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly infer result of || logical expression based on derived values', () => {
const prop = extractProp('<div foo={undefined || baz} />');
const expected = 'baz';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return undefined when evaluating `undefined || undefined` ', () => {
const prop = extractProp('<div foo={undefined || undefined} />');
const expected = undefined;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Member expression', () => {
it('should return string representation of form `object.property`', () => {
const prop = extractProp('<div foo={bar.baz} />');
const expected = 'bar.baz';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate to a correct representation of member expression with a nullable member', () => {
const prop = extractProp('<div foo={bar?.baz} />');
const expected = 'bar?.baz';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Call expression', () => {
it('should return string representation of callee', () => {
const prop = extractProp('<div foo={bar()} />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return string representation of callee', () => {
const prop = extractProp('<div foo={bar.call()} />');
const expected = 'bar.call';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Unary expression', () => {
it('should correctly evaluate an expression that prefixes with -', () => {
const prop = extractProp('<div foo={-bar} />');
// -"bar" => NaN
const expected = true;
const actual = Number.isNaN(getPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with -', () => {
const prop = extractProp('<div foo={-42} />');
const expected = -42;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with +', () => {
const prop = extractProp('<div foo={+bar} />');
// +"bar" => NaN
const expected = true;
const actual = Number.isNaN(getPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with +', () => {
const prop = extractProp('<div foo={+42} />');
const expected = 42;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with !', () => {
const prop = extractProp('<div foo={!bar} />');
const expected = false; // !"bar" === false
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with ~', () => {
const prop = extractProp('<div foo={~bar} />');
const expected = -1; // ~"bar" === -1
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return true when evaluating `delete foo`', () => {
const prop = extractProp('<div foo={delete x} />');
const expected = true;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return undefined when evaluating `void foo`', () => {
const prop = extractProp('<div foo={void x} />');
const expected = undefined;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
// TODO: We should fix this to check to see if we can evaluate it.
it('should return undefined when evaluating `typeof foo`', () => {
const prop = extractProp('<div foo={typeof x} />');
const expected = undefined;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Update expression', () => {
it('should correctly evaluate an expression that prefixes with ++', () => {
const prop = extractProp('<div foo={++bar} />');
// ++"bar" => NaN
const expected = true;
const actual = Number.isNaN(getPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that prefixes with --', () => {
const prop = extractProp('<div foo={--bar} />');
const expected = true;
const actual = Number.isNaN(getPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that suffixes with ++', () => {
const prop = extractProp('<div foo={bar++} />');
// "bar"++ => NaN
const expected = true;
const actual = Number.isNaN(getPropValue(prop));
assert.equal(expected, actual);
});
it('should correctly evaluate an expression that suffixes with --', () => {
const prop = extractProp('<div foo={bar--} />');
const expected = true;
const actual = Number.isNaN(getPropValue(prop));
assert.equal(expected, actual);
});
});
describe('This expression', () => {
it('should return string value `this`', () => {
const prop = extractProp('<div foo={this} />');
const expected = 'this';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Conditional expression', () => {
it('should evaluate the conditional based on the derived values correctly', () => {
const prop = extractProp('<div foo={bar ? baz : bam} />');
const expected = 'baz';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the conditional based on the derived values correctly', () => {
const prop = extractProp('<div foo={undefined ? baz : bam} />');
const expected = 'bam';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the conditional based on the derived values correctly', () => {
const prop = extractProp('<div foo={(1 > 2) ? baz : bam} />');
const expected = 'bam';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Binary expression', () => {
it('should evaluate the `==` operator correctly', () => {
const trueProp = extractProp('<div foo={1 == "1"} />');
const falseProp = extractProp('<div foo={1 == bar} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `!=` operator correctly', () => {
const trueProp = extractProp('<div foo={1 != "2"} />');
const falseProp = extractProp('<div foo={1 != "1"} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `===` operator correctly', () => {
const trueProp = extractProp('<div foo={1 === 1} />');
const falseProp = extractProp('<div foo={1 === "1"} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `!==` operator correctly', () => {
const trueProp = extractProp('<div foo={1 !== "1"} />');
const falseProp = extractProp('<div foo={1 !== 1} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `<` operator correctly', () => {
const trueProp = extractProp('<div foo={1 < 2} />');
const falseProp = extractProp('<div foo={1 < 0} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `>` operator correctly', () => {
const trueProp = extractProp('<div foo={1 > 0} />');
const falseProp = extractProp('<div foo={1 > 2} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `<=` operator correctly', () => {
const trueProp = extractProp('<div foo={1 <= 1} />');
const falseProp = extractProp('<div foo={1 <= 0} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `>=` operator correctly', () => {
const trueProp = extractProp('<div foo={1 >= 1} />');
const falseProp = extractProp('<div foo={1 >= 2} />');
const trueVal = getPropValue(trueProp);
const falseVal = getPropValue(falseProp);
assert.equal(true, trueVal);
assert.equal(false, falseVal);
});
it('should evaluate the `<<` operator correctly', () => {
const prop = extractProp('<div foo={1 << 2} />');
const expected = 4;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `>>` operator correctly', () => {
const prop = extractProp('<div foo={1 >> 2} />');
const expected = 0;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `>>>` operator correctly', () => {
const prop = extractProp('<div foo={2 >>> 1} />');
const expected = 1;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `+` operator correctly', () => {
const prop = extractProp('<div foo={1 + 1} />');
const expected = 2;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `-` operator correctly', () => {
const prop = extractProp('<div foo={1 - 1} />');
const expected = 0;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `*` operator correctly', () => {
const prop = extractProp('<div foo={10 * 10} />');
const expected = 100;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `/` operator correctly', () => {
const prop = extractProp('<div foo={10 / 2} />');
const expected = 5;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `%` operator correctly', () => {
const prop = extractProp('<div foo={10 % 3} />');
const expected = 1;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `|` operator correctly', () => {
const prop = extractProp('<div foo={10 | 1} />');
const expected = 11;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `^` operator correctly', () => {
const prop = extractProp('<div foo={10 ^ 1} />');
const expected = 11;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `&` operator correctly', () => {
const prop = extractProp('<div foo={10 & 1} />');
const expected = 0;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `in` operator correctly', () => {
const prop = extractProp('<div foo={foo in bar} />');
const expected = false;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `instanceof` operator correctly', () => {
const prop = extractProp('<div foo={{} instanceof Object} />');
const expected = true;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should evaluate the `instanceof` operator when right side is not a function', () => {
const prop = extractProp('<div foo={"bar" instanceof Baz} />');
const expected = false;
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
describe('Object expression', () => {
it('should evaluate to a correct representation of the object in props', () => {
const prop = extractProp('<div foo={ { bar: "baz" } } />');
const expected = { bar: 'baz' };
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
it('should evaluate to a correct representation of the object, ignore spread properties', () => {
const prop = extractProp('<div foo={{bar: "baz", ...{baz: "bar", foo: {...{bar: "meh"}}}}} />');
const expected = { bar: 'baz', baz: 'bar', foo: { bar: 'meh' } };
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
it('should evaluate to a correct representation of the object, ignore spread properties', () => {
const prop = extractProp('<div foo={{ pathname: manageRoute, state: {...data}}} />');
const expected = { pathname: 'manageRoute', state: {} };
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
});
describe('New expression', () => {
it('should return a new empty object', () => {
const prop = extractProp('<div foo={new Bar()} />');
const expected = {};
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
});
describe('Array expression', () => {
it('should evaluate to correct representation of the the array in props', () => {
const prop = extractProp('<div foo={["bar", 42, null]} />');
const expected = ['bar', 42, null];
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
it('should evaluate to a correct representation of an array with spread elements', () => {
const prop = extractProp('<div foo={[...this.props.params, bar]} />');
const expected = [undefined, 'bar'];
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
});
it('should return an empty array provided an empty array in props', () => {
const prop = extractProp('<div foo={[]} />');
const expected = [];
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
describe('Bind expression', () => {
it('should return string representation of bind function call when object is null', () => {
const prop = extractProp('<div foo={::this.handleClick} />');
const expected = null;
const actual = getPropValue(prop);
assert.deepEqual(actual, expected);
});
it('should return string representation of bind function call when object is not null', () => {
const prop = extractProp('<div foo={foo::bar} />');
const expected = 'foo';
const actual = getPropValue(prop);
assert.deepEqual(actual, expected);
});
it('should return string representation of bind function call when binding to object properties', () => {
const prop = extractProp('<div foo={a.b::c} />');
const otherProp = extractProp('<div foo={::a.b.c} />');
const expected = 'a.b';
const actual = getPropValue(prop);
const otherExpected = null;
const otherActual = getPropValue(otherProp);
assert.deepEqual(actual, expected);
assert.deepEqual(otherActual, otherExpected);
});
});
describe('Type Cast Expression', () => {
it('should return the expression from a type cast', () => {
const prop = extractProp('<div foo={(this.handleClick: (event: MouseEvent) => void))} />');
const expected = 'this.handleClick';
const actual = getPropValue(prop);
assert.deepEqual(expected, actual);
});
});
describeIfNotBabylon('Typescript', () => {
beforeEach(() => {
changePlugins(pls => [...pls, 'typescript']);
});
it('should return string representation of variable identifier wrapped in a Typescript non-null assertion', () => {
const prop = extractProp('<div foo={bar!} />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return string representation of variable identifier wrapped in a deep Typescript non-null assertion', () => {
const prop = extractProp('<div foo={(bar!)!} />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
it('should return string representation of variable identifier wrapped in a Typescript type coercion', () => {
changePlugins(pls => [...pls, 'typescript']);
const prop = extractProp('<div foo={bar as any} />');
const expected = 'bar';
const actual = getPropValue(prop);
assert.equal(expected, actual);
});
});
});
+412
View File
@@ -0,0 +1,412 @@
/* eslint-env mocha */
import assert from 'assert';
import { getOpeningElement, setParserName } from '../helper';
import hasProp, { hasAnyProp, hasEveryProp } from '../../src/hasProp';
describe('hasProp', () => {
beforeEach(() => {
setParserName('babel');
});
it('should export a function', () => {
const expected = 'function';
const actual = typeof hasProp;
assert.equal(expected, actual);
});
it('should return false if no arguments are provided', () => {
const expected = false;
const actual = hasProp();
assert.equal(expected, actual);
});
it('should return false if the prop is absent', () => {
const code = '<div />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const expected = false;
const actual = hasProp(props, prop);
assert.equal(expected, actual);
});
it('should return true if the prop exists', () => {
const code = '<div id="foo" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const expected = true;
const actual = hasProp(props, prop);
assert.equal(expected, actual);
});
it('should return true if the prop may exist in spread loose mode', () => {
const code = '<div {...props} />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const options = {
spreadStrict: false,
};
const expected = true;
const actual = hasProp(props, prop, options);
assert.equal(expected, actual);
});
it('should return false if the prop is considered absent in case-sensitive mode', () => {
const code = '<div ID="foo" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const options = {
ignoreCase: false,
};
const expected = false;
const actual = hasProp(props, prop, options);
assert.equal(expected, actual);
});
});
describe('hasAnyProp tests', () => {
it('should export a function', () => {
const expected = 'function';
const actual = typeof hasAnyProp;
assert.equal(expected, actual);
});
it('should return false if no arguments are provided', () => {
const expected = false;
const actual = hasAnyProp();
assert.equal(expected, actual);
});
it('should return false if the prop is absent', () => {
const code = '<div />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const expected = false;
const actual = hasAnyProp(props, prop);
assert.equal(expected, actual);
});
it('should return false if all props are absent in array', () => {
const code = '<div />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const propsToCheck = ['id', 'className'];
const expected = false;
const actual = hasAnyProp(props, propsToCheck);
assert.equal(expected, actual);
});
it('should return false if all props are absent in space delimited string', () => {
const code = '<div />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const propsToCheck = 'id className';
const expected = false;
const actual = hasAnyProp(props, propsToCheck);
assert.equal(expected, actual);
});
it('should return true if the prop exists', () => {
const code = '<div id="foo" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const expected = true;
const actual = hasAnyProp(props, prop);
assert.equal(expected, actual);
});
it('should return true if any prop exists in array', () => {
const code = '<div id="foo" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = ['className', 'id'];
const expected = true;
const actual = hasAnyProp(props, prop);
assert.equal(expected, actual);
});
it('should return true if any prop exists in space delimited string', () => {
const code = '<div id="foo" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'className id';
const expected = true;
const actual = hasAnyProp(props, prop);
assert.equal(expected, actual);
});
it('should return true if the prop may exist in spread loose mode', () => {
const code = '<div {...props} />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const options = {
spreadStrict: false,
};
const expected = true;
const actual = hasAnyProp(props, prop, options);
assert.equal(expected, actual);
});
it('should return true if any prop may exist in spread loose mode', () => {
const code = '<div {...props} />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = ['id', 'className'];
const options = {
spreadStrict: false,
};
const expected = true;
const actual = hasAnyProp(props, prop, options);
assert.equal(expected, actual);
});
it('should return false if the prop is considered absent in case-sensitive mode', () => {
const code = '<div ID="foo" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const options = {
ignoreCase: false,
};
const expected = false;
const actual = hasAnyProp(props, prop, options);
assert.equal(expected, actual);
});
it('should return false if all props are considered absent in case-sensitive mode', () => {
const code = '<div ID="foo" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = ['id', 'iD', 'className'];
const options = {
ignoreCase: false,
};
const expected = false;
const actual = hasAnyProp(props, prop, options);
assert.equal(expected, actual);
});
});
describe('hasEveryProp tests', () => {
it('should export a function', () => {
const expected = 'function';
const actual = typeof hasEveryProp;
assert.equal(expected, actual);
});
it('should return true if no arguments are provided', () => {
const expected = true;
const actual = hasEveryProp();
assert.equal(expected, actual);
});
it('should return false if the prop is absent', () => {
const code = '<div />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const expected = false;
const actual = hasEveryProp(props, prop);
assert.equal(expected, actual);
});
it('should return false if any props are absent in array', () => {
const code = '<div id="foo" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const propsToCheck = ['id', 'className'];
const expected = false;
const actual = hasEveryProp(props, propsToCheck);
assert.equal(expected, actual);
});
it('should return false if all props are absent in array', () => {
const code = '<div />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const propsToCheck = ['id', 'className'];
const expected = false;
const actual = hasEveryProp(props, propsToCheck);
assert.equal(expected, actual);
});
it('should return false if any props are absent in space delimited string', () => {
const code = '<div id="foo" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const propsToCheck = 'id className';
const expected = false;
const actual = hasEveryProp(props, propsToCheck);
assert.equal(expected, actual);
});
it('should return false if all props are absent in space delimited string', () => {
const code = '<div />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const propsToCheck = 'id className';
const expected = false;
const actual = hasEveryProp(props, propsToCheck);
assert.equal(expected, actual);
});
it('should return true if the prop exists', () => {
const code = '<div id="foo" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const expected = true;
const actual = hasEveryProp(props, prop);
assert.equal(expected, actual);
});
it('should return true if all props exist in array', () => {
const code = '<div id="foo" className="box" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = ['className', 'id'];
const expected = true;
const actual = hasEveryProp(props, prop);
assert.equal(expected, actual);
});
it('should return true if all props exist in space delimited string', () => {
const code = '<div id="foo" className="box" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'className id';
const expected = true;
const actual = hasEveryProp(props, prop);
assert.equal(expected, actual);
});
it('should return true if the props may exist in spread loose mode', () => {
const code = '<div {...props} />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const options = {
spreadStrict: false,
};
const expected = true;
const actual = hasEveryProp(props, prop, options);
assert.equal(expected, actual);
});
it('should return true if all props may exist in spread loose mode', () => {
const code = '<div {...props} />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = ['id', 'className'];
const options = {
spreadStrict: false,
};
const expected = true;
const actual = hasEveryProp(props, prop, options);
assert.equal(expected, actual);
});
it('should return false if the prop is considered absent in case-sensitive mode', () => {
const code = '<div ID="foo" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = 'id';
const options = {
ignoreCase: false,
};
const expected = false;
const actual = hasEveryProp(props, prop, options);
assert.equal(expected, actual);
});
it('should return false if all props are considered absent in case-sensitive mode', () => {
const code = '<div ID="foo" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = ['id', 'iD', 'className'];
const options = {
ignoreCase: false,
};
const expected = false;
const actual = hasEveryProp(props, prop, options);
assert.equal(expected, actual);
});
it('should return true if all props are considered present in case-sensitive mode', () => {
const code = '<div ID="foo" className="box" />';
const node = getOpeningElement(code);
const { attributes: props } = node;
const prop = ['ID', 'className'];
const options = {
ignoreCase: false,
};
const expected = true;
const actual = hasEveryProp(props, prop, options);
assert.equal(expected, actual);
});
});
+35
View File
@@ -0,0 +1,35 @@
/* eslint-env mocha */
import fs from 'fs';
import path from 'path';
import assert from 'assert';
import core from '../../src/index';
const src = fs.readdirSync(path.resolve(__dirname, '../../src'))
.filter(f => f.indexOf('.js') >= 0)
.map(f => path.basename(f, '.js'));
describe('main export', () => {
it('should export an object', () => {
const expected = 'object';
const actual = typeof core;
assert.equal(expected, actual);
});
src.filter(f => f !== 'index').forEach((f) => {
it(`should export ${f}`, () => {
assert.equal(
core[f],
require(path.join('../../src/', f)).default // eslint-disable-line
);
});
it(`should export ${f} from root`, () => {
const file = `${f}.js`;
const expected = true;
const actual = fs.statSync(path.join(path.resolve('.'), file)).isFile();
assert.equal(expected, actual);
});
});
});
+42
View File
@@ -0,0 +1,42 @@
/* eslint-env mocha */
import assert from 'assert';
import { extractProp, setParserName } from '../helper';
import propName from '../../src/propName';
describe('propName', () => {
beforeEach(() => {
setParserName('babel');
});
it('should export a function', () => {
const expected = 'function';
const actual = typeof propName;
assert.equal(expected, actual);
});
it('should throw an error if the argument is missing', () => {
assert.throws(() => { propName(); }, Error);
});
it('should throw an error if the argument not a JSX node', () => {
assert.throws(() => { propName({ a: 'foo' }); }, Error);
});
it('should return correct name for normal prop', () => {
const prop = extractProp('<div foo="bar" />');
const expected = 'foo';
const actual = propName(prop);
assert.equal(expected, actual);
});
it('should return correct name for namespaced prop', () => {
const prop = extractProp('<div foo:bar="baz" />', 'foo:bar');
const expected = 'foo:bar';
const actual = propName(prop);
assert.equal(expected, actual);
});
});
+1
View File
@@ -0,0 +1 @@
module.exports = require('./lib').elementType; // eslint-disable-line import/no-unresolved
+1
View File
@@ -0,0 +1 @@
module.exports = require('./lib').eventHandlers; // eslint-disable-line import/no-unresolved
+1
View File
@@ -0,0 +1 @@
module.exports = require('./lib').eventHandlersByType; // eslint-disable-line import/no-unresolved
+1
View File
@@ -0,0 +1 @@
module.exports = require('./lib').getLiteralPropValue; // eslint-disable-line import/no-unresolved
+1
View File
@@ -0,0 +1 @@
module.exports = require('./lib').getProp; // eslint-disable-line import/no-unresolved
+1
View File
@@ -0,0 +1 @@
module.exports = require('./lib').getPropValue; // eslint-disable-line import/no-unresolved
+1
View File
@@ -0,0 +1 @@
module.exports = require('./lib').hasAnyProp; // eslint-disable-line import/no-unresolved
+1
View File
@@ -0,0 +1 @@
module.exports = require('./lib').hasEveryProp; // eslint-disable-line import/no-unresolved
+1
View File
@@ -0,0 +1 @@
module.exports = require('./lib').hasProp; // eslint-disable-line import/no-unresolved
+44
View File
@@ -0,0 +1,44 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = elementType;
function resolveMemberExpressions() {
var object = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var property = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (object.type === 'JSXMemberExpression') {
return resolveMemberExpressions(object.object, object.property) + '.' + property.name;
}
return object.name + '.' + property.name;
}
/**
* Returns the tagName associated with a JSXElement.
*/
function elementType() {
var node = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var name = node.name;
if (!name) {
throw new Error('The argument provided is not a JSXElement node.');
}
if (name.type === 'JSXMemberExpression') {
var _name$object = name.object,
object = _name$object === undefined ? {} : _name$object,
_name$property = name.property,
property = _name$property === undefined ? {} : _name$property;
return resolveMemberExpressions(object, property);
}
if (name.type === 'JSXNamespacedName') {
return name.namespace.name + ':' + name.name.name;
}
return node.name.name;
}
+33
View File
@@ -0,0 +1,33 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
/**
* Common event handlers for JSX element event binding.
*/
var eventHandlersByType = {
clipboard: ['onCopy', 'onCut', 'onPaste'],
composition: ['onCompositionEnd', 'onCompositionStart', 'onCompositionUpdate'],
keyboard: ['onKeyDown', 'onKeyPress', 'onKeyUp'],
focus: ['onFocus', 'onBlur'],
form: ['onChange', 'onInput', 'onSubmit'],
mouse: ['onClick', 'onContextMenu', 'onDblClick', 'onDoubleClick', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 'onDragLeave', 'onDragOver', 'onDragStart', 'onDrop', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 'onMouseMove', 'onMouseOut', 'onMouseOver', 'onMouseUp'],
selection: ['onSelect'],
touch: ['onTouchCancel', 'onTouchEnd', 'onTouchMove', 'onTouchStart'],
ui: ['onScroll'],
wheel: ['onWheel'],
media: ['onAbort', 'onCanPlay', 'onCanPlayThrough', 'onDurationChange', 'onEmptied', 'onEncrypted', 'onEnded', 'onError', 'onLoadedData', 'onLoadedMetadata', 'onLoadStart', 'onPause', 'onPlay', 'onPlaying', 'onProgress', 'onRateChange', 'onSeeked', 'onSeeking', 'onStalled', 'onSuspend', 'onTimeUpdate', 'onVolumeChange', 'onWaiting'],
image: ['onLoad', 'onError'],
animation: ['onAnimationStart', 'onAnimationEnd', 'onAnimationIteration'],
transition: ['onTransitionEnd']
};
var eventHandlers = Object.keys(eventHandlersByType).reduce(function (accumulator, type) {
return accumulator.concat(eventHandlersByType[type]);
}, []);
exports.default = eventHandlers;
exports.eventHandlersByType = eventHandlersByType;
+40
View File
@@ -0,0 +1,40 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = getProp;
var _propName = require('./propName');
var _propName2 = _interopRequireDefault(_propName);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var DEFAULT_OPTIONS = {
ignoreCase: true
};
/**
* Returns the JSXAttribute itself or undefined, indicating the prop
* is not present on the JSXOpeningElement.
*
*/
function getProp() {
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var prop = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_OPTIONS;
var propToFind = options.ignoreCase ? prop.toUpperCase() : prop;
return props.find(function (attribute) {
// If the props contain a spread prop, then skip.
if (attribute.type === 'JSXSpreadAttribute') {
return false;
}
var currentProp = options.ignoreCase ? (0, _propName2.default)(attribute).toUpperCase() : (0, _propName2.default)(attribute);
return propToFind === currentProp;
});
}
+57
View File
@@ -0,0 +1,57 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = getPropValue;
exports.getLiteralPropValue = getLiteralPropValue;
var _values = require('./values');
var _values2 = _interopRequireDefault(_values);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var extractValue = function extractValue(attribute, extractor) {
if (attribute && attribute.type === 'JSXAttribute') {
if (attribute.value === null) {
// Null valued attributes imply truthiness.
// For example: <div aria-hidden />
// See: https://facebook.github.io/react/docs/jsx-in-depth.html#boolean-attributes
return true;
}
return extractor(attribute.value);
}
return undefined;
};
/**
* Returns the value of a given attribute.
* Different types of attributes have their associated
* values in different properties on the object.
*
* This function should return the most *closely* associated
* value with the intention of the JSX.
*
* @param attribute - The JSXAttribute collected by AST parser.
*/
function getPropValue(attribute) {
return extractValue(attribute, _values2.default);
}
/**
* Returns the value of a given attribute.
* Different types of attributes have their associated
* values in different properties on the object.
*
* This function should return a value only if we can extract
* a literal value from its attribute (i.e. values that have generic
* types in JavaScript - strings, numbers, booleans, etc.)
*
* @param attribute - The JSXAttribute collected by AST parser.
*/
function getLiteralPropValue(attribute) {
return extractValue(attribute, _values.getLiteralValue);
}
+74
View File
@@ -0,0 +1,74 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = hasProp;
exports.hasAnyProp = hasAnyProp;
exports.hasEveryProp = hasEveryProp;
var _propName = require('./propName');
var _propName2 = _interopRequireDefault(_propName);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var DEFAULT_OPTIONS = {
spreadStrict: true,
ignoreCase: true
};
/**
* Returns boolean indicating whether an prop exists on the props
* property of a JSX element node.
*/
function hasProp() {
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var prop = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_OPTIONS;
var propToCheck = options.ignoreCase ? prop.toUpperCase() : prop;
return props.some(function (attribute) {
// If the props contain a spread prop, then refer to strict param.
if (attribute.type === 'JSXSpreadAttribute') {
return !options.spreadStrict;
}
var currentProp = options.ignoreCase ? (0, _propName2.default)(attribute).toUpperCase() : (0, _propName2.default)(attribute);
return propToCheck === currentProp;
});
}
/**
* Given the props on a node and a list of props to check, this returns a boolean
* indicating if any of them exist on the node.
*/
function hasAnyProp() {
var nodeProps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_OPTIONS;
var propsToCheck = typeof props === 'string' ? props.split(' ') : props;
return propsToCheck.some(function (prop) {
return hasProp(nodeProps, prop, options);
});
}
/**
* Given the props on a node and a list of props to check, this returns a boolean
* indicating if all of them exist on the node
*/
function hasEveryProp() {
var nodeProps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_OPTIONS;
var propsToCheck = typeof props === 'string' ? props.split(' ') : props;
return propsToCheck.every(function (prop) {
return hasProp(nodeProps, prop, options);
});
}
+40
View File
@@ -0,0 +1,40 @@
'use strict';
var _hasProp = require('./hasProp');
var _hasProp2 = _interopRequireDefault(_hasProp);
var _elementType = require('./elementType');
var _elementType2 = _interopRequireDefault(_elementType);
var _eventHandlers = require('./eventHandlers');
var _eventHandlers2 = _interopRequireDefault(_eventHandlers);
var _getProp = require('./getProp');
var _getProp2 = _interopRequireDefault(_getProp);
var _getPropValue = require('./getPropValue');
var _getPropValue2 = _interopRequireDefault(_getPropValue);
var _propName = require('./propName');
var _propName2 = _interopRequireDefault(_propName);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
module.exports = {
hasProp: _hasProp2.default,
hasAnyProp: _hasProp.hasAnyProp,
hasEveryProp: _hasProp.hasEveryProp,
elementType: _elementType2.default,
eventHandlers: _eventHandlers2.default,
eventHandlersByType: _eventHandlers.eventHandlersByType,
getProp: _getProp2.default,
getPropValue: _getPropValue2.default,
getLiteralPropValue: _getPropValue.getLiteralPropValue,
propName: _propName2.default
};
+22
View File
@@ -0,0 +1,22 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = propName;
/**
* Returns the name of the prop given the JSXAttribute object.
*/
function propName() {
var prop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
if (!prop.type || prop.type !== 'JSXAttribute') {
throw new Error('The prop must be a JSXAttribute collected by the AST parser.');
}
if (prop.name.type === 'JSXNamespacedName') {
return prop.name.namespace.name + ':' + prop.name.name.name;
}
return prop.name.name;
}
+14
View File
@@ -0,0 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromJSXElement;
/**
* Extractor function for a JSXElement type value node.
*
* Returns self-closing element with correct name.
*/
function extractValueFromJSXElement(value) {
return "<" + value.openingElement.name.name + " />";
}
+27
View File
@@ -0,0 +1,27 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromLiteral;
/**
* Extractor function for a Literal type value node.
*
* @param - value - AST Value object with type `Literal`
* @returns { String|Boolean } - The extracted value converted to correct type.
*/
function extractValueFromLiteral(value) {
var extractedValue = value.value;
var normalizedStringValue = typeof extractedValue === 'string' && extractedValue.toLowerCase();
if (normalizedStringValue === 'true') {
return true;
}
if (normalizedStringValue === 'false') {
return false;
}
return extractedValue;
}
+19
View File
@@ -0,0 +1,19 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromArrayExpression;
/**
* Extractor function for an ArrayExpression type value node.
* An array expression is an expression with [] syntax.
*
* @returns - An array of the extracted elements.
*/
function extractValueFromArrayExpression(value) {
// eslint-disable-next-line global-require
var getValue = require('./index.js').default;
return value.elements.map(function (element) {
return getValue(element);
});
}
+78
View File
@@ -0,0 +1,78 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromBinaryExpression;
/**
* Extractor function for a BinaryExpression type value node.
* A binary expression has a left and right side separated by an operator
* such as `a + b`.
*
* @param - value - AST Value object with type `BinaryExpression`
* @returns - The extracted value converted to correct type.
*/
function extractValueFromBinaryExpression(value) {
// eslint-disable-next-line global-require
var getValue = require('./index.js').default;
var operator = value.operator,
left = value.left,
right = value.right;
var leftVal = getValue(left);
var rightVal = getValue(right);
switch (operator) {
case '==':
return leftVal == rightVal; // eslint-disable-line
case '!=':
return leftVal != rightVal; // eslint-disable-line
case '===':
return leftVal === rightVal;
case '!==':
return leftVal !== rightVal;
case '<':
return leftVal < rightVal;
case '<=':
return leftVal <= rightVal;
case '>':
return leftVal > rightVal;
case '>=':
return leftVal >= rightVal;
case '<<':
return leftVal << rightVal; // eslint-disable-line no-bitwise
case '>>':
return leftVal >> rightVal; // eslint-disable-line no-bitwise
case '>>>':
return leftVal >>> rightVal; // eslint-disable-line no-bitwise
case '+':
return leftVal + rightVal;
case '-':
return leftVal - rightVal;
case '*':
return leftVal * rightVal;
case '/':
return leftVal / rightVal;
case '%':
return leftVal % rightVal;
case '|':
return leftVal | rightVal; // eslint-disable-line no-bitwise
case '^':
return leftVal ^ rightVal; // eslint-disable-line no-bitwise
case '&':
return leftVal & rightVal; // eslint-disable-line no-bitwise
case 'in':
try {
return leftVal in rightVal;
} catch (err) {
return false;
}
case 'instanceof':
if (typeof rightVal !== 'function') {
return false;
}
return leftVal instanceof rightVal;
default:
return undefined;
}
}
+30
View File
@@ -0,0 +1,30 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromBindExpression;
/**
* Extractor function for a BindExpression type value node.
* A bind expression looks like `::this.foo`
* This will return `this.foo.bind(this)` as the value to indicate its existence,
* since we can not execute the function this.foo.bind(this) in a static environment.
*
* @param - value - AST Value object with type `BindExpression`
* @returns - The extracted value converted to correct type.
*/
function extractValueFromBindExpression(value) {
// eslint-disable-next-line global-require
var getValue = require('./index.js').default;
var callee = getValue(value.callee);
// If value.object === null, the callee must be a MemberExpression.
// https://github.com/babel/babylon/blob/master/ast/spec.md#bindexpression
var object = value.object === null ? getValue(value.callee.object) : getValue(value.object);
if (value.object && value.object.property) {
return object + '.' + callee + '.bind(' + object + ')';
}
return callee + '.bind(' + object + ')';
}
+20
View File
@@ -0,0 +1,20 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromCallExpression;
/**
* Extractor function for a CallExpression type value node.
* A call expression looks like `bar()`
* This will return `bar` as the value to indicate its existence,
* since we can not execute the function bar in a static environment.
*
* @param - value - AST Value object with type `CallExpression`
* @returns - The extracted value converted to correct type.
*/
function extractValueFromCallExpression(value) {
// eslint-disable-next-line global-require
var getValue = require('./index.js').default;
return getValue(value.callee);
}
@@ -0,0 +1,22 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromConditionalExpression;
/**
* Extractor function for a ConditionalExpression type value node.
*
* @param - value - AST Value object with type `ConditionalExpression`
* @returns - The extracted value converted to correct type.
*/
function extractValueFromConditionalExpression(value) {
// eslint-disable-next-line global-require
var getValue = require('./index.js').default;
var test = value.test,
alternate = value.alternate,
consequent = value.consequent;
return getValue(test) ? getValue(consequent) : getValue(alternate);
}
@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromFunctionExpression;
/**
* Extractor function for a FunctionExpression type value node.
* Statically, we can't execute the given function, so just return a function
* to indicate that the value is present.
*
* @param - value - AST Value object with type `FunctionExpression`
* @returns - The extracted value converted to correct type.
*/
function extractValueFromFunctionExpression(value) {
return function () {
return value;
};
}
+35
View File
@@ -0,0 +1,35 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromIdentifier;
var JS_RESERVED = {
Array: Array,
Date: Date,
Infinity: Infinity,
Math: Math,
Number: Number,
Object: Object,
String: String,
undefined: undefined
};
/**
* Extractor function for a Identifier type value node.
* An Identifier is usually a reference to a variable.
* Just return variable name to determine its existence.
*
* @param - value - AST Value object with type `Identifier`
* @returns - The extracted value converted to correct type.
*/
function extractValueFromIdentifier(value) {
var name = value.name;
if (Object.hasOwnProperty.call(JS_RESERVED, name)) {
return JS_RESERVED[name];
}
return name;
}
+26
View File
@@ -0,0 +1,26 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromLogicalExpression;
/**
* Extractor function for a LogicalExpression type value node.
* A logical expression is `a && b` or `a || b`, so we evaluate both sides
* and return the extracted value of the expression.
*
* @param - value - AST Value object with type `LogicalExpression`
* @returns - The extracted value converted to correct type.
*/
function extractValueFromLogicalExpression(value) {
// eslint-disable-next-line global-require
var getValue = require('./index.js').default;
var operator = value.operator,
left = value.left,
right = value.right;
var leftVal = getValue(left);
var rightVal = getValue(right);
return operator === '&&' ? leftVal && rightVal : leftVal || rightVal;
}
+19
View File
@@ -0,0 +1,19 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromMemberExpression;
/**
* Extractor function for a MemberExpression type value node.
* A member expression is accessing a property on an object `obj.property`.
*
* @param - value - AST Value object with type `MemberExpression`
* @returns - The extracted value converted to correct type
* and maintaing `obj.property` convention.
*/
function extractValueFromMemberExpression(value) {
// eslint-disable-next-line global-require
var getValue = require('./index.js').default;
return getValue(value.object) + '.' + getValue(value.property);
}
+15
View File
@@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromNewExpression;
/**
* Extractor function for a NewExpression type value node.
* A new expression instantiates an object with `new` keyword.
*
* @returns - an empty object.
*/
function extractValueFromNewExpression() {
return new Object(); // eslint-disable-line
}
+35
View File
@@ -0,0 +1,35 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromObjectExpression;
var _object = require('object.assign');
var _object2 = _interopRequireDefault(_object);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Extractor function for an ObjectExpression type value node.
* An object expression is using {}.
*
* @returns - a representation of the object
*/
function extractValueFromObjectExpression(value) {
// eslint-disable-next-line global-require
var getValue = require('./index.js').default;
return value.properties.reduce(function (obj, property) {
var object = (0, _object2.default)({}, obj);
// Support types: SpreadProperty and ExperimentalSpreadProperty
if (/^(?:Experimental)?Spread(?:Property|Element)$/.test(property.type)) {
if (property.argument.type === 'ObjectExpression') {
return (0, _object2.default)(object, extractValueFromObjectExpression(property.argument));
}
} else {
object[getValue(property.key)] = getValue(property.value);
}
return object;
}, {});
}
@@ -0,0 +1,19 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromOptionalMemberExpression;
/**
* Extractor function for a OptionalMemberExpression type value node.
* A member expression is accessing a property on an object `obj.property`.
*
* @param - value - AST Value object with type `OptionalMemberExpression`
* @returns - The extracted value converted to correct type
* and maintaing `obj?.property` convention.
*/
function extractValueFromOptionalMemberExpression(value) {
// eslint-disable-next-line global-require
var getValue = require('./index.js').default;
return getValue(value.object) + '?.' + getValue(value.property);
}
+17
View File
@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromSpreadElement;
/**
* Extractor function for a SpreadElement type value node.
* We can't statically evaluate an array spread, so just return
* undefined.
*
* @param - value - AST Value object with type `SpreadElement`
* @returns - An prototypeless object.
*/
function extractValueFromSpreadElement() {
return undefined;
}
@@ -0,0 +1,20 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromTaggedTemplateExpression;
var _TemplateLiteral = require('./TemplateLiteral');
var _TemplateLiteral2 = _interopRequireDefault(_TemplateLiteral);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Returns the string value of a tagged template literal object.
* Redirects the bulk of the work to `TemplateLiteral`.
*/
function extractValueFromTaggedTemplateExpression(value) {
return (0, _TemplateLiteral2.default)(value.quasi);
}
+40
View File
@@ -0,0 +1,40 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromTemplateLiteral;
/**
* Returns the string value of a template literal object.
* Tries to build it as best as it can based on the passed
* prop. For instance `This is a ${prop}` will return 'This is a {prop}'.
*
* If the template literal builds to undefined (`${undefined}`), then
* this should return "undefined".
*/
function extractValueFromTemplateLiteral(value) {
var quasis = value.quasis,
expressions = value.expressions;
var partitions = quasis.concat(expressions);
return partitions.sort(function (a, b) {
return a.start - b.start;
}).reduce(function (raw, part) {
var type = part.type;
if (type === 'TemplateElement') {
return raw + part.value.raw;
}
if (type === 'Identifier') {
return part.name === 'undefined' ? '' + raw + part.name : raw + '{' + part.name + '}';
}
if (type.indexOf('Expression') > -1) {
return raw + '{' + type + '}';
}
return raw;
}, '');
}
+15
View File
@@ -0,0 +1,15 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromThisExpression;
/**
* Extractor function for a ThisExpression type value node.
* A this expression is using `this` as an identifier.
*
* @returns - 'this' as a string.
*/
function extractValueFromThisExpression() {
return 'this';
}
@@ -0,0 +1,19 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromTypeCastExpression;
/**
* Extractor function for a TypeCastExpression type value node.
* A type cast expression looks like `(this.handleClick: (event: MouseEvent) => void))`
* This will return the expression `this.handleClick`.
*
* @param - value - AST Value object with type `TypeCastExpression`
* @returns - The extracted value converted to correct type.
*/
function extractValueFromTypeCastExpression(value) {
// eslint-disable-next-line global-require
var getValue = require('./index.js').default;
return getValue(value.expression);
}
+39
View File
@@ -0,0 +1,39 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromUnaryExpression;
/**
* Extractor function for a UnaryExpression type value node.
* A unary expression is an expression with a unary operator.
* For example, !"foobar" will evaluate to false, so this will return false.
*
* @param - value - AST Value object with type `UnaryExpression`
* @returns - The extracted value converted to correct type.
*/
function extractValueFromUnaryExpression(value) {
// eslint-disable-next-line global-require
var getValue = require('./index.js').default;
var operator = value.operator,
argument = value.argument;
switch (operator) {
case '-':
return -getValue(argument);
case '+':
return +getValue(argument); // eslint-disable-line no-implicit-coercion
case '!':
return !getValue(argument);
case '~':
return ~getValue(argument); // eslint-disable-line no-bitwise
case 'delete':
// I believe delete statements evaluate to true.
return true;
case 'typeof':
case 'void':
default:
return undefined;
}
}
+33
View File
@@ -0,0 +1,33 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromUpdateExpression;
/**
* Extractor function for an UpdateExpression type value node.
* An update expression is an expression with an update operator.
* For example, foo++ will evaluate to foo + 1.
*
* @param - value - AST Value object with type `UpdateExpression`
* @returns - The extracted value converted to correct type.
*/
function extractValueFromUpdateExpression(value) {
// eslint-disable-next-line global-require
var getValue = require('./index.js').default;
var operator = value.operator,
argument = value.argument,
prefix = value.prefix;
var val = getValue(argument);
switch (operator) {
case '++':
return prefix ? ++val : val++; // eslint-disable-line no-plusplus
case '--':
return prefix ? --val : val--; // eslint-disable-line no-plusplus
default:
return undefined;
}
}
+244
View File
@@ -0,0 +1,244 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extract;
exports.extractLiteral = extractLiteral;
var _object = require('object.assign');
var _object2 = _interopRequireDefault(_object);
var _Literal = require('../Literal');
var _Literal2 = _interopRequireDefault(_Literal);
var _JSXElement = require('../JSXElement');
var _JSXElement2 = _interopRequireDefault(_JSXElement);
var _Identifier = require('./Identifier');
var _Identifier2 = _interopRequireDefault(_Identifier);
var _TaggedTemplateExpression = require('./TaggedTemplateExpression');
var _TaggedTemplateExpression2 = _interopRequireDefault(_TaggedTemplateExpression);
var _TemplateLiteral = require('./TemplateLiteral');
var _TemplateLiteral2 = _interopRequireDefault(_TemplateLiteral);
var _FunctionExpression = require('./FunctionExpression');
var _FunctionExpression2 = _interopRequireDefault(_FunctionExpression);
var _LogicalExpression = require('./LogicalExpression');
var _LogicalExpression2 = _interopRequireDefault(_LogicalExpression);
var _MemberExpression = require('./MemberExpression');
var _MemberExpression2 = _interopRequireDefault(_MemberExpression);
var _OptionalMemberExpression = require('./OptionalMemberExpression');
var _OptionalMemberExpression2 = _interopRequireDefault(_OptionalMemberExpression);
var _CallExpression = require('./CallExpression');
var _CallExpression2 = _interopRequireDefault(_CallExpression);
var _UnaryExpression = require('./UnaryExpression');
var _UnaryExpression2 = _interopRequireDefault(_UnaryExpression);
var _ThisExpression = require('./ThisExpression');
var _ThisExpression2 = _interopRequireDefault(_ThisExpression);
var _ConditionalExpression = require('./ConditionalExpression');
var _ConditionalExpression2 = _interopRequireDefault(_ConditionalExpression);
var _BinaryExpression = require('./BinaryExpression');
var _BinaryExpression2 = _interopRequireDefault(_BinaryExpression);
var _ObjectExpression = require('./ObjectExpression');
var _ObjectExpression2 = _interopRequireDefault(_ObjectExpression);
var _NewExpression = require('./NewExpression');
var _NewExpression2 = _interopRequireDefault(_NewExpression);
var _UpdateExpression = require('./UpdateExpression');
var _UpdateExpression2 = _interopRequireDefault(_UpdateExpression);
var _ArrayExpression = require('./ArrayExpression');
var _ArrayExpression2 = _interopRequireDefault(_ArrayExpression);
var _BindExpression = require('./BindExpression');
var _BindExpression2 = _interopRequireDefault(_BindExpression);
var _SpreadElement = require('./SpreadElement');
var _SpreadElement2 = _interopRequireDefault(_SpreadElement);
var _TypeCastExpression = require('./TypeCastExpression');
var _TypeCastExpression2 = _interopRequireDefault(_TypeCastExpression);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// Composition map of types to their extractor functions.
var TYPES = {
Identifier: _Identifier2.default,
Literal: _Literal2.default,
JSXElement: _JSXElement2.default,
TaggedTemplateExpression: _TaggedTemplateExpression2.default,
TemplateLiteral: _TemplateLiteral2.default,
ArrowFunctionExpression: _FunctionExpression2.default,
FunctionExpression: _FunctionExpression2.default,
LogicalExpression: _LogicalExpression2.default,
MemberExpression: _MemberExpression2.default,
OptionalMemberExpression: _OptionalMemberExpression2.default,
CallExpression: _CallExpression2.default,
UnaryExpression: _UnaryExpression2.default,
ThisExpression: _ThisExpression2.default,
ConditionalExpression: _ConditionalExpression2.default,
BinaryExpression: _BinaryExpression2.default,
ObjectExpression: _ObjectExpression2.default,
NewExpression: _NewExpression2.default,
UpdateExpression: _UpdateExpression2.default,
ArrayExpression: _ArrayExpression2.default,
BindExpression: _BindExpression2.default,
SpreadElement: _SpreadElement2.default,
TypeCastExpression: _TypeCastExpression2.default
};
var noop = function noop() {
return null;
};
var errorMessage = function errorMessage(expression) {
return 'The prop value with an expression type of ' + expression + ' could not be resolved. Please file issue to get this fixed immediately.';
};
/**
* This function maps an AST value node
* to its correct extractor function for its
* given type.
*
* This will map correctly for *all* possible expression types.
*
* @param - value - AST Value object with type `JSXExpressionContainer`
* @returns The extracted value.
*/
function extract(value) {
// Value will not have the expression property when we recurse.
// The type for expression on ArrowFunctionExpression is a boolean.
var expression = void 0;
if (typeof value.expression !== 'boolean' && value.expression) {
expression = value.expression; // eslint-disable-line prefer-destructuring
} else {
expression = value;
}
var _expression = expression,
type = _expression.type;
while (type === 'TSNonNullExpression' || type === 'TSAsExpression') {
var _expression2 = expression;
type = _expression2.type;
if (expression.expression) {
var _expression3 = expression;
expression = _expression3.expression;
}
}
if (TYPES[type] === undefined) {
// eslint-disable-next-line no-console
console.error(errorMessage(type));
return null;
}
return TYPES[type](expression);
}
// Composition map of types to their extractor functions to handle literals.
var LITERAL_TYPES = (0, _object2.default)({}, TYPES, {
Literal: function Literal(value) {
var extractedVal = TYPES.Literal.call(undefined, value);
var isNull = extractedVal === null;
// This will be convention for attributes that have null
// value explicitly defined (<div prop={null} /> maps to 'null').
return isNull ? 'null' : extractedVal;
},
Identifier: function Identifier(value) {
var isUndefined = TYPES.Identifier.call(undefined, value) === undefined;
return isUndefined ? undefined : null;
},
JSXElement: noop,
ArrowFunctionExpression: noop,
FunctionExpression: noop,
LogicalExpression: noop,
MemberExpression: noop,
OptionalMemberExpression: noop,
CallExpression: noop,
UnaryExpression: function UnaryExpression(value) {
var extractedVal = TYPES.UnaryExpression.call(undefined, value);
return extractedVal === undefined ? null : extractedVal;
},
UpdateExpression: function UpdateExpression(value) {
var extractedVal = TYPES.UpdateExpression.call(undefined, value);
return extractedVal === undefined ? null : extractedVal;
},
ThisExpression: noop,
ConditionalExpression: noop,
BinaryExpression: noop,
ObjectExpression: noop,
NewExpression: noop,
ArrayExpression: function ArrayExpression(value) {
var extractedVal = TYPES.ArrayExpression.call(undefined, value);
return extractedVal.filter(function (val) {
return val !== null;
});
},
BindExpression: noop,
SpreadElement: noop,
TSNonNullExpression: noop,
TSAsExpression: noop,
TypeCastExpression: noop
});
/**
* This function maps an AST value node
* to its correct extractor function for its
* given type.
*
* This will map correctly for *some* possible types that map to literals.
*
* @param - value - AST Value object with type `JSXExpressionContainer`
* @returns The extracted value.
*/
function extractLiteral(value) {
// Value will not have the expression property when we recurse.
var expression = value.expression || value;
var type = expression.type;
if (LITERAL_TYPES[type] === undefined) {
// eslint-disable-next-line no-console
console.error(errorMessage(type));
return null;
}
return LITERAL_TYPES[type](expression);
}
+66
View File
@@ -0,0 +1,66 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = getValue;
exports.getLiteralValue = getLiteralValue;
var _object = require('object.assign');
var _object2 = _interopRequireDefault(_object);
var _Literal = require('./Literal');
var _Literal2 = _interopRequireDefault(_Literal);
var _JSXElement = require('./JSXElement');
var _JSXElement2 = _interopRequireDefault(_JSXElement);
var _expressions = require('./expressions');
var _expressions2 = _interopRequireDefault(_expressions);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// Composition map of types to their extractor functions.
var TYPES = {
Literal: _Literal2.default,
JSXElement: _JSXElement2.default,
JSXExpressionContainer: _expressions2.default
};
// Composition map of types to their extractor functions to handle literals.
var LITERAL_TYPES = (0, _object2.default)({}, TYPES, {
JSXElement: function JSXElement() {
return null;
},
JSXExpressionContainer: _expressions.extractLiteral
});
/**
* This function maps an AST value node
* to its correct extractor function for its
* given type.
*
* This will map correctly for *all* possible types.
*
* @param value - AST Value object on a JSX Attribute.
*/
function getValue(value) {
return TYPES[value.type](value);
}
/**
* This function maps an AST value node
* to its correct extractor function for its
* given type.
*
* This will map correctly for *some* possible types that map to literals.
*
* @param value - AST Value object on a JSX Attribute.
*/
function getLiteralValue(value) {
return LITERAL_TYPES[value.type](value);
}
+102
View File
@@ -0,0 +1,102 @@
{
"_args": [
[
"jsx-ast-utils@2.2.1",
"/home/app"
]
],
"_development": true,
"_from": "jsx-ast-utils@2.2.1",
"_id": "jsx-ast-utils@2.2.1",
"_inBundle": false,
"_integrity": "sha512-v3FxCcAf20DayI+uxnCuw795+oOIkVu6EnJ1+kSzhqqTZHNkTZ7B66ZgLp4oLJ/gbA64cI0B7WRoHZMSRdyVRQ==",
"_location": "/jsx-ast-utils",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "jsx-ast-utils@2.2.1",
"name": "jsx-ast-utils",
"escapedName": "jsx-ast-utils",
"rawSpec": "2.2.1",
"saveSpec": null,
"fetchSpec": "2.2.1"
},
"_requiredBy": [
"/eslint-plugin-jsx-a11y",
"/eslint-plugin-react"
],
"_resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz",
"_spec": "2.2.1",
"_where": "/home/app",
"author": {
"name": "Ethan Cohen"
},
"bugs": {
"url": "https://github.com/evcohen/jsx-ast-utils/issues"
},
"dependencies": {
"array-includes": "^3.0.3",
"object.assign": "^4.1.0"
},
"description": "AST utility module for statically analyzing JSX",
"devDependencies": {
"@babel/parser": "^7.4.4",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-eslint": "^10.0.2",
"babel-jest": "^20.0.3",
"babel-plugin-transform-replace-object-assign": "^1.0.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babylon": "^6.18.0",
"coveralls": "^3.0.4",
"eslint": "^6.0.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-plugin-import": "^2.17.2",
"flow-parser": "^0.102.0",
"in-publish": "^2.0.0",
"jest": "^20.0.4",
"jest-cli": "^20.0.4",
"rimraf": "^2.6.3"
},
"engines": {
"node": ">=4.0"
},
"homepage": "https://github.com/evcohen/jsx-ast-utils#readme",
"jest": {
"coverageReporters": [
"lcov"
],
"coverageDirectory": "reports",
"testPathIgnorePatterns": [
"/node_modules/",
"helper.js"
]
},
"keywords": [
"jsx",
"ast",
"lint",
"eslint"
],
"license": "MIT",
"main": "lib/index.js",
"name": "jsx-ast-utils",
"repository": {
"type": "git",
"url": "git+https://github.com/evcohen/jsx-ast-utils.git"
},
"scripts": {
"build": "babel src --out-dir lib",
"coveralls": "cat ./reports/lcov.info | coveralls",
"lint": "eslint .",
"prebuild": "rimraf lib",
"prepublish": "not-in-publish || (npm test && npm run build)",
"pretest": "npm run lint",
"test": "npm run tests-only --",
"test:watch": "npm test -- --watch",
"tests-only": "jest --coverage"
},
"version": "2.2.1"
}
+1
View File
@@ -0,0 +1 @@
module.exports = require('./lib').propName; // eslint-disable-line import/no-unresolved
+29
View File
@@ -0,0 +1,29 @@
function resolveMemberExpressions(object = {}, property = {}) {
if (object.type === 'JSXMemberExpression') {
return `${resolveMemberExpressions(object.object, object.property)}.${property.name}`;
}
return `${object.name}.${property.name}`;
}
/**
* Returns the tagName associated with a JSXElement.
*/
export default function elementType(node = {}) {
const { name } = node;
if (!name) {
throw new Error('The argument provided is not a JSXElement node.');
}
if (name.type === 'JSXMemberExpression') {
const { object = {}, property = {} } = name;
return resolveMemberExpressions(object, property);
}
if (name.type === 'JSXNamespacedName') {
return `${name.namespace.name}:${name.name.name}`;
}
return node.name.name;
}
+113
View File
@@ -0,0 +1,113 @@
/**
* Common event handlers for JSX element event binding.
*/
const eventHandlersByType = {
clipboard: [
'onCopy',
'onCut',
'onPaste',
],
composition: [
'onCompositionEnd',
'onCompositionStart',
'onCompositionUpdate',
],
keyboard: [
'onKeyDown',
'onKeyPress',
'onKeyUp',
],
focus: [
'onFocus',
'onBlur',
],
form: [
'onChange',
'onInput',
'onSubmit',
],
mouse: [
'onClick',
'onContextMenu',
'onDblClick',
'onDoubleClick',
'onDrag',
'onDragEnd',
'onDragEnter',
'onDragExit',
'onDragLeave',
'onDragOver',
'onDragStart',
'onDrop',
'onMouseDown',
'onMouseEnter',
'onMouseLeave',
'onMouseMove',
'onMouseOut',
'onMouseOver',
'onMouseUp',
],
selection: [
'onSelect',
],
touch: [
'onTouchCancel',
'onTouchEnd',
'onTouchMove',
'onTouchStart',
],
ui: [
'onScroll',
],
wheel: [
'onWheel',
],
media: [
'onAbort',
'onCanPlay',
'onCanPlayThrough',
'onDurationChange',
'onEmptied',
'onEncrypted',
'onEnded',
'onError',
'onLoadedData',
'onLoadedMetadata',
'onLoadStart',
'onPause',
'onPlay',
'onPlaying',
'onProgress',
'onRateChange',
'onSeeked',
'onSeeking',
'onStalled',
'onSuspend',
'onTimeUpdate',
'onVolumeChange',
'onWaiting',
],
image: [
'onLoad',
'onError',
],
animation: [
'onAnimationStart',
'onAnimationEnd',
'onAnimationIteration',
],
transition: [
'onTransitionEnd',
],
};
const eventHandlers = Object.keys(eventHandlersByType).reduce(
(accumulator, type) => accumulator.concat(eventHandlersByType[type]),
[],
);
export default eventHandlers;
export { eventHandlersByType };
+27
View File
@@ -0,0 +1,27 @@
import propName from './propName';
const DEFAULT_OPTIONS = {
ignoreCase: true,
};
/**
* Returns the JSXAttribute itself or undefined, indicating the prop
* is not present on the JSXOpeningElement.
*
*/
export default function getProp(props = [], prop = '', options = DEFAULT_OPTIONS) {
const propToFind = options.ignoreCase ? prop.toUpperCase() : prop;
return props.find((attribute) => {
// If the props contain a spread prop, then skip.
if (attribute.type === 'JSXSpreadAttribute') {
return false;
}
const currentProp = options.ignoreCase
? propName(attribute).toUpperCase()
: propName(attribute);
return propToFind === currentProp;
});
}
+45
View File
@@ -0,0 +1,45 @@
import getValue, { getLiteralValue } from './values';
const extractValue = (attribute, extractor) => {
if (attribute && attribute.type === 'JSXAttribute') {
if (attribute.value === null) {
// Null valued attributes imply truthiness.
// For example: <div aria-hidden />
// See: https://facebook.github.io/react/docs/jsx-in-depth.html#boolean-attributes
return true;
}
return extractor(attribute.value);
}
return undefined;
};
/**
* Returns the value of a given attribute.
* Different types of attributes have their associated
* values in different properties on the object.
*
* This function should return the most *closely* associated
* value with the intention of the JSX.
*
* @param attribute - The JSXAttribute collected by AST parser.
*/
export default function getPropValue(attribute) {
return extractValue(attribute, getValue);
}
/**
* Returns the value of a given attribute.
* Different types of attributes have their associated
* values in different properties on the object.
*
* This function should return a value only if we can extract
* a literal value from its attribute (i.e. values that have generic
* types in JavaScript - strings, numbers, booleans, etc.)
*
* @param attribute - The JSXAttribute collected by AST parser.
*/
export function getLiteralPropValue(attribute) {
return extractValue(attribute, getLiteralValue);
}
+47
View File
@@ -0,0 +1,47 @@
import propName from './propName';
const DEFAULT_OPTIONS = {
spreadStrict: true,
ignoreCase: true,
};
/**
* Returns boolean indicating whether an prop exists on the props
* property of a JSX element node.
*/
export default function hasProp(props = [], prop = '', options = DEFAULT_OPTIONS) {
const propToCheck = options.ignoreCase ? prop.toUpperCase() : prop;
return props.some((attribute) => {
// If the props contain a spread prop, then refer to strict param.
if (attribute.type === 'JSXSpreadAttribute') {
return !options.spreadStrict;
}
const currentProp = options.ignoreCase
? propName(attribute).toUpperCase()
: propName(attribute);
return propToCheck === currentProp;
});
}
/**
* Given the props on a node and a list of props to check, this returns a boolean
* indicating if any of them exist on the node.
*/
export function hasAnyProp(nodeProps = [], props = [], options = DEFAULT_OPTIONS) {
const propsToCheck = typeof props === 'string' ? props.split(' ') : props;
return propsToCheck.some(prop => hasProp(nodeProps, prop, options));
}
/**
* Given the props on a node and a list of props to check, this returns a boolean
* indicating if all of them exist on the node
*/
export function hasEveryProp(nodeProps = [], props = [], options = DEFAULT_OPTIONS) {
const propsToCheck = typeof props === 'string' ? props.split(' ') : props;
return propsToCheck.every(prop => hasProp(nodeProps, prop, options));
}
+19
View File
@@ -0,0 +1,19 @@
import hasProp, { hasAnyProp, hasEveryProp } from './hasProp';
import elementType from './elementType';
import eventHandlers, { eventHandlersByType } from './eventHandlers';
import getProp from './getProp';
import getPropValue, { getLiteralPropValue } from './getPropValue';
import propName from './propName';
module.exports = {
hasProp,
hasAnyProp,
hasEveryProp,
elementType,
eventHandlers,
eventHandlersByType,
getProp,
getPropValue,
getLiteralPropValue,
propName,
};
+14
View File
@@ -0,0 +1,14 @@
/**
* Returns the name of the prop given the JSXAttribute object.
*/
export default function propName(prop = {}) {
if (!prop.type || prop.type !== 'JSXAttribute') {
throw new Error('The prop must be a JSXAttribute collected by the AST parser.');
}
if (prop.name.type === 'JSXNamespacedName') {
return `${prop.name.namespace.name}:${prop.name.name.name}`;
}
return prop.name.name;
}
+8
View File
@@ -0,0 +1,8 @@
/**
* Extractor function for a JSXElement type value node.
*
* Returns self-closing element with correct name.
*/
export default function extractValueFromJSXElement(value) {
return `<${value.openingElement.name.name} />`;
}
+20
View File
@@ -0,0 +1,20 @@
/**
* Extractor function for a Literal type value node.
*
* @param - value - AST Value object with type `Literal`
* @returns { String|Boolean } - The extracted value converted to correct type.
*/
export default function extractValueFromLiteral(value) {
const { value: extractedValue } = value;
const normalizedStringValue = typeof extractedValue === 'string' && extractedValue.toLowerCase();
if (normalizedStringValue === 'true') {
return true;
}
if (normalizedStringValue === 'false') {
return false;
}
return extractedValue;
}
+11
View File
@@ -0,0 +1,11 @@
/**
* Extractor function for an ArrayExpression type value node.
* An array expression is an expression with [] syntax.
*
* @returns - An array of the extracted elements.
*/
export default function extractValueFromArrayExpression(value) {
// eslint-disable-next-line global-require
const getValue = require('./index.js').default;
return value.elements.map(element => getValue(element));
}
+69
View File
@@ -0,0 +1,69 @@
/**
* Extractor function for a BinaryExpression type value node.
* A binary expression has a left and right side separated by an operator
* such as `a + b`.
*
* @param - value - AST Value object with type `BinaryExpression`
* @returns - The extracted value converted to correct type.
*/
export default function extractValueFromBinaryExpression(value) {
// eslint-disable-next-line global-require
const getValue = require('./index.js').default;
const { operator, left, right } = value;
const leftVal = getValue(left);
const rightVal = getValue(right);
switch (operator) {
case '==':
return leftVal == rightVal; // eslint-disable-line
case '!=':
return leftVal != rightVal; // eslint-disable-line
case '===':
return leftVal === rightVal;
case '!==':
return leftVal !== rightVal;
case '<':
return leftVal < rightVal;
case '<=':
return leftVal <= rightVal;
case '>':
return leftVal > rightVal;
case '>=':
return leftVal >= rightVal;
case '<<':
return leftVal << rightVal; // eslint-disable-line no-bitwise
case '>>':
return leftVal >> rightVal; // eslint-disable-line no-bitwise
case '>>>':
return leftVal >>> rightVal; // eslint-disable-line no-bitwise
case '+':
return leftVal + rightVal;
case '-':
return leftVal - rightVal;
case '*':
return leftVal * rightVal;
case '/':
return leftVal / rightVal;
case '%':
return leftVal % rightVal;
case '|':
return leftVal | rightVal; // eslint-disable-line no-bitwise
case '^':
return leftVal ^ rightVal; // eslint-disable-line no-bitwise
case '&':
return leftVal & rightVal; // eslint-disable-line no-bitwise
case 'in':
try {
return leftVal in rightVal;
} catch (err) {
return false;
}
case 'instanceof':
if (typeof rightVal !== 'function') {
return false;
}
return leftVal instanceof rightVal;
default:
return undefined;
}
}
+24
View File
@@ -0,0 +1,24 @@
/**
* Extractor function for a BindExpression type value node.
* A bind expression looks like `::this.foo`
* This will return `this.foo.bind(this)` as the value to indicate its existence,
* since we can not execute the function this.foo.bind(this) in a static environment.
*
* @param - value - AST Value object with type `BindExpression`
* @returns - The extracted value converted to correct type.
*/
export default function extractValueFromBindExpression(value) {
// eslint-disable-next-line global-require
const getValue = require('./index.js').default;
const callee = getValue(value.callee);
// If value.object === null, the callee must be a MemberExpression.
// https://github.com/babel/babylon/blob/master/ast/spec.md#bindexpression
const object = value.object === null ? getValue(value.callee.object) : getValue(value.object);
if (value.object && value.object.property) {
return `${object}.${callee}.bind(${object})`;
}
return `${callee}.bind(${object})`;
}
+14
View File
@@ -0,0 +1,14 @@
/**
* Extractor function for a CallExpression type value node.
* A call expression looks like `bar()`
* This will return `bar` as the value to indicate its existence,
* since we can not execute the function bar in a static environment.
*
* @param - value - AST Value object with type `CallExpression`
* @returns - The extracted value converted to correct type.
*/
export default function extractValueFromCallExpression(value) {
// eslint-disable-next-line global-require
const getValue = require('./index.js').default;
return getValue(value.callee);
}
@@ -0,0 +1,17 @@
/**
* Extractor function for a ConditionalExpression type value node.
*
* @param - value - AST Value object with type `ConditionalExpression`
* @returns - The extracted value converted to correct type.
*/
export default function extractValueFromConditionalExpression(value) {
// eslint-disable-next-line global-require
const getValue = require('./index.js').default;
const {
test,
alternate,
consequent,
} = value;
return getValue(test) ? getValue(consequent) : getValue(alternate);
}
@@ -0,0 +1,11 @@
/**
* Extractor function for a FunctionExpression type value node.
* Statically, we can't execute the given function, so just return a function
* to indicate that the value is present.
*
* @param - value - AST Value object with type `FunctionExpression`
* @returns - The extracted value converted to correct type.
*/
export default function extractValueFromFunctionExpression(value) {
return () => value;
}
+28
View File
@@ -0,0 +1,28 @@
const JS_RESERVED = {
Array,
Date,
Infinity,
Math,
Number,
Object,
String,
undefined,
};
/**
* Extractor function for a Identifier type value node.
* An Identifier is usually a reference to a variable.
* Just return variable name to determine its existence.
*
* @param - value - AST Value object with type `Identifier`
* @returns - The extracted value converted to correct type.
*/
export default function extractValueFromIdentifier(value) {
const { name } = value;
if (Object.hasOwnProperty.call(JS_RESERVED, name)) {
return JS_RESERVED[name];
}
return name;
}
+17
View File
@@ -0,0 +1,17 @@
/**
* Extractor function for a LogicalExpression type value node.
* A logical expression is `a && b` or `a || b`, so we evaluate both sides
* and return the extracted value of the expression.
*
* @param - value - AST Value object with type `LogicalExpression`
* @returns - The extracted value converted to correct type.
*/
export default function extractValueFromLogicalExpression(value) {
// eslint-disable-next-line global-require
const getValue = require('./index.js').default;
const { operator, left, right } = value;
const leftVal = getValue(left);
const rightVal = getValue(right);
return operator === '&&' ? leftVal && rightVal : leftVal || rightVal;
}
+13
View File
@@ -0,0 +1,13 @@
/**
* Extractor function for a MemberExpression type value node.
* A member expression is accessing a property on an object `obj.property`.
*
* @param - value - AST Value object with type `MemberExpression`
* @returns - The extracted value converted to correct type
* and maintaing `obj.property` convention.
*/
export default function extractValueFromMemberExpression(value) {
// eslint-disable-next-line global-require
const getValue = require('./index.js').default;
return `${getValue(value.object)}.${getValue(value.property)}`;
}
+9
View File
@@ -0,0 +1,9 @@
/**
* Extractor function for a NewExpression type value node.
* A new expression instantiates an object with `new` keyword.
*
* @returns - an empty object.
*/
export default function extractValueFromNewExpression() {
return new Object(); // eslint-disable-line
}
+24
View File
@@ -0,0 +1,24 @@
import assign from 'object.assign';
/**
* Extractor function for an ObjectExpression type value node.
* An object expression is using {}.
*
* @returns - a representation of the object
*/
export default function extractValueFromObjectExpression(value) {
// eslint-disable-next-line global-require
const getValue = require('./index.js').default;
return value.properties.reduce((obj, property) => {
const object = Object.assign({}, obj);
// Support types: SpreadProperty and ExperimentalSpreadProperty
if (/^(?:Experimental)?Spread(?:Property|Element)$/.test(property.type)) {
if (property.argument.type === 'ObjectExpression') {
return assign(object, extractValueFromObjectExpression(property.argument));
}
} else {
object[getValue(property.key)] = getValue(property.value);
}
return object;
}, {});
}
@@ -0,0 +1,13 @@
/**
* Extractor function for a OptionalMemberExpression type value node.
* A member expression is accessing a property on an object `obj.property`.
*
* @param - value - AST Value object with type `OptionalMemberExpression`
* @returns - The extracted value converted to correct type
* and maintaing `obj?.property` convention.
*/
export default function extractValueFromOptionalMemberExpression(value) {
// eslint-disable-next-line global-require
const getValue = require('./index.js').default;
return `${getValue(value.object)}?.${getValue(value.property)}`;
}
+11
View File
@@ -0,0 +1,11 @@
/**
* Extractor function for a SpreadElement type value node.
* We can't statically evaluate an array spread, so just return
* undefined.
*
* @param - value - AST Value object with type `SpreadElement`
* @returns - An prototypeless object.
*/
export default function extractValueFromSpreadElement() {
return undefined;
}
@@ -0,0 +1,9 @@
import extractValueFromTemplateLiteral from './TemplateLiteral';
/**
* Returns the string value of a tagged template literal object.
* Redirects the bulk of the work to `TemplateLiteral`.
*/
export default function extractValueFromTaggedTemplateExpression(value) {
return extractValueFromTemplateLiteral(value.quasi);
}
+34
View File
@@ -0,0 +1,34 @@
/**
* Returns the string value of a template literal object.
* Tries to build it as best as it can based on the passed
* prop. For instance `This is a ${prop}` will return 'This is a {prop}'.
*
* If the template literal builds to undefined (`${undefined}`), then
* this should return "undefined".
*/
export default function extractValueFromTemplateLiteral(value) {
const {
quasis,
expressions,
} = value;
const partitions = quasis.concat(expressions);
return partitions.sort((a, b) => a.start - b.start).reduce((raw, part) => {
const {
type,
} = part;
if (type === 'TemplateElement') {
return raw + part.value.raw;
}
if (type === 'Identifier') {
return part.name === 'undefined' ? `${raw}${part.name}` : `${raw}{${part.name}}`;
}
if (type.indexOf('Expression') > -1) {
return `${raw}{${type}}`;
}
return raw;
}, '');
}
+9
View File
@@ -0,0 +1,9 @@
/**
* Extractor function for a ThisExpression type value node.
* A this expression is using `this` as an identifier.
*
* @returns - 'this' as a string.
*/
export default function extractValueFromThisExpression() {
return 'this';
}
@@ -0,0 +1,13 @@
/**
* Extractor function for a TypeCastExpression type value node.
* A type cast expression looks like `(this.handleClick: (event: MouseEvent) => void))`
* This will return the expression `this.handleClick`.
*
* @param - value - AST Value object with type `TypeCastExpression`
* @returns - The extracted value converted to correct type.
*/
export default function extractValueFromTypeCastExpression(value) {
// eslint-disable-next-line global-require
const getValue = require('./index.js').default;
return getValue(value.expression);
}
+31
View File
@@ -0,0 +1,31 @@
/**
* Extractor function for a UnaryExpression type value node.
* A unary expression is an expression with a unary operator.
* For example, !"foobar" will evaluate to false, so this will return false.
*
* @param - value - AST Value object with type `UnaryExpression`
* @returns - The extracted value converted to correct type.
*/
export default function extractValueFromUnaryExpression(value) {
// eslint-disable-next-line global-require
const getValue = require('./index.js').default;
const { operator, argument } = value;
switch (operator) {
case '-':
return -getValue(argument);
case '+':
return +getValue(argument); // eslint-disable-line no-implicit-coercion
case '!':
return !getValue(argument);
case '~':
return ~getValue(argument); // eslint-disable-line no-bitwise
case 'delete':
// I believe delete statements evaluate to true.
return true;
case 'typeof':
case 'void':
default:
return undefined;
}
}
+24
View File
@@ -0,0 +1,24 @@
/**
* Extractor function for an UpdateExpression type value node.
* An update expression is an expression with an update operator.
* For example, foo++ will evaluate to foo + 1.
*
* @param - value - AST Value object with type `UpdateExpression`
* @returns - The extracted value converted to correct type.
*/
export default function extractValueFromUpdateExpression(value) {
// eslint-disable-next-line global-require
const getValue = require('./index.js').default;
const { operator, argument, prefix } = value;
let val = getValue(argument);
switch (operator) {
case '++':
return prefix ? ++val : val++; // eslint-disable-line no-plusplus
case '--':
return prefix ? --val : val--; // eslint-disable-line no-plusplus
default:
return undefined;
}
}
+159
View File
@@ -0,0 +1,159 @@
import Literal from '../Literal';
import JSXElement from '../JSXElement';
import Identifier from './Identifier';
import TaggedTemplateExpression from './TaggedTemplateExpression';
import TemplateLiteral from './TemplateLiteral';
import FunctionExpression from './FunctionExpression';
import LogicalExpression from './LogicalExpression';
import MemberExpression from './MemberExpression';
import OptionalMemberExpression from './OptionalMemberExpression';
import CallExpression from './CallExpression';
import UnaryExpression from './UnaryExpression';
import ThisExpression from './ThisExpression';
import ConditionalExpression from './ConditionalExpression';
import BinaryExpression from './BinaryExpression';
import ObjectExpression from './ObjectExpression';
import NewExpression from './NewExpression';
import UpdateExpression from './UpdateExpression';
import ArrayExpression from './ArrayExpression';
import BindExpression from './BindExpression';
import SpreadElement from './SpreadElement';
import TypeCastExpression from './TypeCastExpression';
// Composition map of types to their extractor functions.
const TYPES = {
Identifier,
Literal,
JSXElement,
TaggedTemplateExpression,
TemplateLiteral,
ArrowFunctionExpression: FunctionExpression,
FunctionExpression,
LogicalExpression,
MemberExpression,
OptionalMemberExpression,
CallExpression,
UnaryExpression,
ThisExpression,
ConditionalExpression,
BinaryExpression,
ObjectExpression,
NewExpression,
UpdateExpression,
ArrayExpression,
BindExpression,
SpreadElement,
TypeCastExpression,
};
const noop = () => null;
const errorMessage = expression => `The prop value with an expression type of ${expression} could not be resolved. Please file issue to get this fixed immediately.`;
/**
* This function maps an AST value node
* to its correct extractor function for its
* given type.
*
* This will map correctly for *all* possible expression types.
*
* @param - value - AST Value object with type `JSXExpressionContainer`
* @returns The extracted value.
*/
export default function extract(value) {
// Value will not have the expression property when we recurse.
// The type for expression on ArrowFunctionExpression is a boolean.
let expression;
if (
typeof value.expression !== 'boolean'
&& value.expression
) {
expression = value.expression; // eslint-disable-line prefer-destructuring
} else {
expression = value;
}
let { type } = expression;
while (type === 'TSNonNullExpression' || type === 'TSAsExpression') {
({ type } = expression);
if (expression.expression) {
({ expression } = expression);
}
}
if (TYPES[type] === undefined) {
// eslint-disable-next-line no-console
console.error(errorMessage(type));
return null;
}
return TYPES[type](expression);
}
// Composition map of types to their extractor functions to handle literals.
const LITERAL_TYPES = Object.assign({}, TYPES, {
Literal: (value) => {
const extractedVal = TYPES.Literal.call(undefined, value);
const isNull = extractedVal === null;
// This will be convention for attributes that have null
// value explicitly defined (<div prop={null} /> maps to 'null').
return isNull ? 'null' : extractedVal;
},
Identifier: (value) => {
const isUndefined = TYPES.Identifier.call(undefined, value) === undefined;
return isUndefined ? undefined : null;
},
JSXElement: noop,
ArrowFunctionExpression: noop,
FunctionExpression: noop,
LogicalExpression: noop,
MemberExpression: noop,
OptionalMemberExpression: noop,
CallExpression: noop,
UnaryExpression: (value) => {
const extractedVal = TYPES.UnaryExpression.call(undefined, value);
return extractedVal === undefined ? null : extractedVal;
},
UpdateExpression: (value) => {
const extractedVal = TYPES.UpdateExpression.call(undefined, value);
return extractedVal === undefined ? null : extractedVal;
},
ThisExpression: noop,
ConditionalExpression: noop,
BinaryExpression: noop,
ObjectExpression: noop,
NewExpression: noop,
ArrayExpression: (value) => {
const extractedVal = TYPES.ArrayExpression.call(undefined, value);
return extractedVal.filter(val => val !== null);
},
BindExpression: noop,
SpreadElement: noop,
TSNonNullExpression: noop,
TSAsExpression: noop,
TypeCastExpression: noop,
});
/**
* This function maps an AST value node
* to its correct extractor function for its
* given type.
*
* This will map correctly for *some* possible types that map to literals.
*
* @param - value - AST Value object with type `JSXExpressionContainer`
* @returns The extracted value.
*/
export function extractLiteral(value) {
// Value will not have the expression property when we recurse.
const expression = value.expression || value;
const { type } = expression;
if (LITERAL_TYPES[type] === undefined) {
// eslint-disable-next-line no-console
console.error(errorMessage(type));
return null;
}
return LITERAL_TYPES[type](expression);
}
+42
View File
@@ -0,0 +1,42 @@
import Literal from './Literal';
import JSXElement from './JSXElement';
import JSXExpressionContainer, { extractLiteral } from './expressions';
// Composition map of types to their extractor functions.
const TYPES = {
Literal,
JSXElement,
JSXExpressionContainer,
};
// Composition map of types to their extractor functions to handle literals.
const LITERAL_TYPES = Object.assign({}, TYPES, {
JSXElement: () => null,
JSXExpressionContainer: extractLiteral,
});
/**
* This function maps an AST value node
* to its correct extractor function for its
* given type.
*
* This will map correctly for *all* possible types.
*
* @param value - AST Value object on a JSX Attribute.
*/
export default function getValue(value) {
return TYPES[value.type](value);
}
/**
* This function maps an AST value node
* to its correct extractor function for its
* given type.
*
* This will map correctly for *some* possible types that map to literals.
*
* @param value - AST Value object on a JSX Attribute.
*/
export function getLiteralValue(value) {
return LITERAL_TYPES[value.type](value);
}