Added logging, changed some directory structure

This commit is contained in:
2018-01-13 21:33:40 -05:00
parent f079a5f067
commit 8e72ffb917
73656 changed files with 35284 additions and 53718 deletions

View File

@@ -0,0 +1,3 @@
{
"presets": ["es2015"]
}

View File

@@ -0,0 +1,3 @@
node_modules/
reports/
lib/

View File

@@ -0,0 +1,3 @@
{
extends: "airbnb-base"
}

View File

@@ -0,0 +1,6 @@
node_modules
reports
npm-debug.log
coverage
.gitignore
.DS_Store

View File

@@ -0,0 +1,11 @@
language: node_js
node_js:
- 4
- 5
- 6
cache:
yarn: true
directories:
- node_modules
after_success:
- npm run coveralls

View File

@@ -0,0 +1,69 @@
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

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.

View File

@@ -0,0 +1,292 @@
<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.
### 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' ],
}
*/
```

View File

@@ -0,0 +1,19 @@
import getProp from '../src/getProp';
const acorn = require('acorn-jsx');
function parse(code) {
return acorn.parse(code, {
plugins: { jsx: true },
});
}
export function getOpeningElement(code) {
return parse(code).body[0].expression.openingElement;
}
export function extractProp(code, prop = 'foo') {
const node = getOpeningElement(code);
const { attributes: props } = node;
return getProp(props, prop);
}

View File

@@ -0,0 +1,82 @@
/* eslint-env mocha */
import assert from 'assert';
import { getOpeningElement } from '../helper';
import elementType from '../../src/elementType';
describe('elementType tests', () => {
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);
});
});

View File

@@ -0,0 +1,100 @@
/* eslint-env mocha */
import assert from 'assert';
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 => eventHandlers.includes(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]));
});
});

View File

@@ -0,0 +1,71 @@
/* eslint-env mocha */
import assert from 'assert';
import { getOpeningElement } from '../helper';
import getProp from '../../src/getProp';
describe('getProp', () => {
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);
});
});

View File

@@ -0,0 +1,457 @@
/* eslint-env mocha */
/* eslint no-template-curly-in-string: 0 */
import assert from 'assert';
import { extractProp } from '../helper';
import { getLiteralPropValue } from '../../src/getPropValue';
describe('getLiteralPropValue', () => {
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 throw error when trying to get value from unknown node type', () => {
const prop = {
type: 'JSXAttribute',
value: {
type: 'JSXExpressionContainer',
},
};
assert.throws(() => {
getLiteralPropValue(prop);
}, Error);
});
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 drop variables in template literals that are literally undefined', () => {
const prop = extractProp('<div foo={`bar ${undefined}`} />');
const expected = 'bar ';
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 drop variables in template literals that are literally undefined', () => {
const prop = extractProp('<div foo={noop`bar ${undefined}`} />');
const expected = 'bar ';
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 = 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 = 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 = 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 = 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 = 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 = 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);
});
});

View File

@@ -0,0 +1,803 @@
/* eslint-env mocha */
/* eslint no-template-curly-in-string: 0 */
import assert from 'assert';
import { extractProp } from '../helper';
import getPropValue from '../../src/getPropValue';
describe('getPropValue', () => {
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 error when trying to get value from unknown node type', () => {
const prop = {
type: 'JSXAttribute',
value: {
type: 'JSXExpressionContainer',
},
};
assert.throws(() => {
getPropValue(prop);
}, Error);
});
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 drop variables in template literals that are literally undefined', () => {
const prop = extractProp('<div foo={`bar ${undefined}`} />');
const expected = 'bar ';
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 drop variables in template literals that are literally undefined', () => {
const prop = extractProp('<div foo={noop`bar ${undefined}`} />');
const expected = 'bar ';
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);
});
});
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 = 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 = 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 = 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 = 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 = 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 = 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);
});
});
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 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);
});
});

View File

@@ -0,0 +1,409 @@
/* eslint-env mocha */
import assert from 'assert';
import { getOpeningElement } from '../helper';
import hasProp, { hasAnyProp, hasEveryProp } from '../../src/hasProp';
describe('hasProp', () => {
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);
});
});

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);
});
});
});

View File

@@ -0,0 +1,54 @@
/* eslint-env mocha */
import assert from 'assert';
import { extractProp } from '../helper';
import propName from '../../src/propName';
describe('propName', () => {
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);
});
// Note: this shouldn't happen, but safe guard anyway.
it('should return undefined if prop name is not JSXIdentifier or JSXNamespacedName', () => {
const prop = {
type: 'JSXAttribute',
name: {
type: 'Literal',
},
};
const expected = undefined;
const actual = propName(prop);
assert.equal(expected, actual);
});
});

View File

@@ -0,0 +1 @@
module.exports = require('./lib').elementType; // eslint-disable-line import/no-unresolved

View File

@@ -0,0 +1 @@
module.exports = require('./lib').eventHandlers; // eslint-disable-line import/no-unresolved

View File

@@ -0,0 +1 @@
module.exports = require('./lib').eventHandlersByType; // eslint-disable-line import/no-unresolved

View File

@@ -0,0 +1 @@
module.exports = require('./lib').getLiteralPropValue; // eslint-disable-line import/no-unresolved

View File

@@ -0,0 +1 @@
module.exports = require('./lib').getProp; // eslint-disable-line import/no-unresolved

View File

@@ -0,0 +1 @@
module.exports = require('./lib').getPropValue; // eslint-disable-line import/no-unresolved

View File

@@ -0,0 +1 @@
module.exports = require('./lib').hasAnyProp; // eslint-disable-line import/no-unresolved

View File

@@ -0,0 +1 @@
module.exports = require('./lib').hasEveryProp; // eslint-disable-line import/no-unresolved

View File

@@ -0,0 +1 @@
module.exports = require('./lib').hasProp; // eslint-disable-line import/no-unresolved

View File

@@ -0,0 +1,42 @@
'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);
} else if (name.type === 'JSXNamespacedName') {
return name.namespace.name + ':' + name.name.name;
}
return node.name.name;
}

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;

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;
});
}

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);
}

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);
});
}

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
};

View File

@@ -0,0 +1,25 @@
'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.');
}
switch (prop.name.type) {
case 'JSXIdentifier':
return prop.name.name;
case 'JSXNamespacedName':
return prop.name.namespace.name + ':' + prop.name.name.name;
default:
return undefined;
}
}

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 + " />";
}

View File

@@ -0,0 +1,25 @@
'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;
} else if (normalizedStringValue === 'false') {
return false;
}
return extractedValue;
}

View File

@@ -0,0 +1,24 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromArrayExpression;
var _index = require('./index');
var _index2 = _interopRequireDefault(_index);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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) {
return value.elements.map(function (element) {
return (0, _index2.default)(element);
});
}

View File

@@ -0,0 +1,83 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromBinaryExpression;
var _index = require('./index');
var _index2 = _interopRequireDefault(_index);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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) {
var operator = value.operator,
left = value.left,
right = value.right;
var leftVal = (0, _index2.default)(left);
var rightVal = (0, _index2.default)(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;
}
}

View File

@@ -0,0 +1,25 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromCallExpression;
var _index = require('./index');
var _index2 = _interopRequireDefault(_index);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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) {
return (0, _index2.default)(value.callee);
}

View File

@@ -0,0 +1,27 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromConditionalExpression;
var _index = require('./index');
var _index2 = _interopRequireDefault(_index);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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) {
var test = value.test,
alternate = value.alternate,
consequent = value.consequent;
return (0, _index2.default)(test) ? (0, _index2.default)(consequent) : (0, _index2.default)(alternate);
}

View File

@@ -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;
};
}

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;
}

View File

@@ -0,0 +1,31 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromLogicalExpression;
var _index = require('./index');
var _index2 = _interopRequireDefault(_index);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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) {
var operator = value.operator,
left = value.left,
right = value.right;
var leftVal = (0, _index2.default)(left);
var rightVal = (0, _index2.default)(right);
return operator === '&&' ? leftVal && rightVal : leftVal || rightVal;
}

View File

@@ -0,0 +1,24 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromMemberExpression;
var _index = require('./index');
var _index2 = _interopRequireDefault(_index);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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) {
return (0, _index2.default)(value.object) + '.' + (0, _index2.default)(value.property);
}

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
}

View File

@@ -0,0 +1,26 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromObjectExpression;
var _index = require('./index');
var _index2 = _interopRequireDefault(_index);
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) {
return value.properties.reduce(function (obj, property) {
var object = Object.assign({}, obj);
object[(0, _index2.default)(property.key)] = (0, _index2.default)(property.value);
return object;
}, {});
}

View File

@@ -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);
}

View File

@@ -0,0 +1,36 @@
'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 "".
*/
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;
} else if (type === 'Identifier') {
return part.name === 'undefined' ? raw : raw + '{' + part.name + '}';
} else if (type.indexOf('Expression') > -1) {
return raw + '{' + type + '}';
}
return raw;
}, '');
}

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';
}

View File

@@ -0,0 +1,44 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromUnaryExpression;
var _index = require('./index');
var _index2 = _interopRequireDefault(_index);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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) {
var operator = value.operator,
argument = value.argument;
switch (operator) {
case '-':
return -(0, _index2.default)(argument);
case '+':
return +(0, _index2.default)(argument); // eslint-disable-line no-implicit-coercion
case '!':
return !(0, _index2.default)(argument);
case '~':
return ~(0, _index2.default)(argument); // eslint-disable-line no-bitwise
case 'delete':
// I believe delete statements evaluate to true.
return true;
case 'typeof':
case 'void':
default:
return undefined;
}
}

View File

@@ -0,0 +1,38 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extractValueFromUpdateExpression;
var _index = require('./index');
var _index2 = _interopRequireDefault(_index);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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) {
var operator = value.operator,
argument = value.argument,
prefix = value.prefix;
var val = (0, _index2.default)(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;
}
}

View File

@@ -0,0 +1,200 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extract;
exports.extractLiteral = extractLiteral;
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 _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);
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,
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
};
var noop = function noop() {
return null;
};
// Composition map of types to their extractor functions to handle literals.
var LITERAL_TYPES = Object.assign({}, 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,
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;
});
}
});
var errorMessage = function errorMessage(expression) {
return 'The prop value with an expression type of ' + expression + ' could not be resolved.\n 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;
} else {
expression = value;
}
var _expression = expression,
type = _expression.type;
if (TYPES[type] === undefined) {
throw new Error(errorMessage(type));
}
return TYPES[type](expression);
}
/**
* 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) {
throw new Error(errorMessage(type));
}
return LITERAL_TYPES[type](expression);
}

View File

@@ -0,0 +1,62 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = getValue;
exports.getLiteralValue = getLiteralValue;
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 = Object.assign({}, 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);
}

View File

@@ -0,0 +1,90 @@
{
"_args": [
[
"jsx-ast-utils@1.4.1",
"C:\\Users\\deranjer\\go\\src\\github.com\\deranjer\\goTorrent\\torrent-project"
]
],
"_from": "jsx-ast-utils@1.4.1",
"_id": "jsx-ast-utils@1.4.1",
"_inBundle": false,
"_integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=",
"_location": "/react-scripts/jsx-ast-utils",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "jsx-ast-utils@1.4.1",
"name": "jsx-ast-utils",
"escapedName": "jsx-ast-utils",
"rawSpec": "1.4.1",
"saveSpec": null,
"fetchSpec": "1.4.1"
},
"_requiredBy": [
"/react-scripts/eslint-plugin-jsx-a11y"
],
"_resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz",
"_spec": "1.4.1",
"_where": "C:\\Users\\deranjer\\go\\src\\github.com\\deranjer\\goTorrent\\torrent-project",
"author": {
"name": "Ethan Cohen"
},
"bugs": {
"url": "https://github.com/evcohen/jsx-ast-utils/issues"
},
"description": "AST utility module for statically analyzing JSX",
"devDependencies": {
"acorn-jsx": "^3.0.1",
"babel-cli": "^6.14.0",
"babel-core": "^6.14.0",
"babel-eslint": "^7.0.0",
"babel-jest": "^19.0.0",
"babel-polyfill": "^6.16.0",
"babel-preset-es2015": "^6.14.0",
"coveralls": "^2.11.8",
"eslint": "^3.12.1",
"eslint-config-airbnb-base": "^11.1.0",
"eslint-plugin-import": "^2.2.0",
"jest": "^19.0.0",
"rimraf": "^2.5.2"
},
"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": "rimraf lib && babel src --out-dir lib",
"coveralls": "cat ./reports/lcov.info | coveralls",
"lint": "eslint --config .eslintrc .",
"lint:fix": "npm run lint -- --fix",
"prepublish": "npm run lint && npm run test && npm run build",
"pretest": "npm run lint",
"test": "jest --coverage",
"test:watch": "npm test -- --watch"
},
"version": "1.4.1"
}

View File

@@ -0,0 +1 @@
module.exports = require('./lib').propName; // eslint-disable-line import/no-unresolved

View File

@@ -0,0 +1,27 @@
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);
} else if (name.type === 'JSXNamespacedName') {
return `${name.namespace.name}:${name.name.name}`;
}
return node.name.name;
}

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 };

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;
});
}

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);
}

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));
}

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,
};

View File

@@ -0,0 +1,17 @@
/**
* 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.');
}
switch (prop.name.type) {
case 'JSXIdentifier':
return prop.name.name;
case 'JSXNamespacedName':
return `${prop.name.namespace.name}:${prop.name.name.name}`;
default:
return undefined;
}
}

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} />`;
}

View File

@@ -0,0 +1,18 @@
/**
* 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;
} else if (normalizedStringValue === 'false') {
return false;
}
return extractedValue;
}

View File

@@ -0,0 +1,11 @@
import getValue from './index';
/**
* 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) {
return value.elements.map(element => getValue(element));
}

View File

@@ -0,0 +1,70 @@
import getValue from './index';
/**
* 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) {
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;
}
}

View File

@@ -0,0 +1,14 @@
import getValue from './index';
/**
* 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) {
return getValue(value.callee);
}

View File

@@ -0,0 +1,17 @@
import getValue from './index';
/**
* 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) {
const {
test,
alternate,
consequent,
} = value;
return getValue(test) ? getValue(consequent) : getValue(alternate);
}

View File

@@ -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;
}

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;
}

View File

@@ -0,0 +1,17 @@
import getValue from './index';
/**
* 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) {
const { operator, left, right } = value;
const leftVal = getValue(left);
const rightVal = getValue(right);
return operator === '&&' ? leftVal && rightVal : leftVal || rightVal;
}

View File

@@ -0,0 +1,13 @@
import getValue from './index';
/**
* 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) {
return `${getValue(value.object)}.${getValue(value.property)}`;
}

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
}

View File

@@ -0,0 +1,15 @@
import getValue from './index';
/**
* Extractor function for an ObjectExpression type value node.
* An object expression is using {}.
*
* @returns - a representation of the object
*/
export default function extractValueFromObjectExpression(value) {
return value.properties.reduce((obj, property) => {
const object = Object.assign({}, obj);
object[getValue(property.key)] = getValue(property.value);
return object;
}, {});
}

View File

@@ -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);
}

View File

@@ -0,0 +1,30 @@
/**
* 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 "".
*/
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;
} else if (type === 'Identifier') {
return part.name === 'undefined' ? raw : `${raw}{${part.name}}`;
} else if (type.indexOf('Expression') > -1) {
return `${raw}{${type}}`;
}
return raw;
}, '');
}

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';
}

View File

@@ -0,0 +1,31 @@
import getValue from './index';
/**
* 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) {
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;
}
}

View File

@@ -0,0 +1,24 @@
import getValue from './index';
/**
* 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) {
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;
}
}

View File

@@ -0,0 +1,136 @@
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 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';
// Composition map of types to their extractor functions.
const TYPES = {
Identifier,
Literal,
JSXElement,
TaggedTemplateExpression,
TemplateLiteral,
ArrowFunctionExpression: FunctionExpression,
FunctionExpression,
LogicalExpression,
MemberExpression,
CallExpression,
UnaryExpression,
ThisExpression,
ConditionalExpression,
BinaryExpression,
ObjectExpression,
NewExpression,
UpdateExpression,
ArrayExpression,
};
const noop = () => null;
// 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,
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);
},
});
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;
} else {
expression = value;
}
const { type } = expression;
if (TYPES[type] === undefined) {
throw new Error(errorMessage(type));
}
return TYPES[type](expression);
}
/**
* 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) {
throw new Error(errorMessage(type));
}
return LITERAL_TYPES[type](expression);
}

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);
}

File diff suppressed because it is too large Load Diff