Skip to content

Commit

Permalink
Merge branch 'master' of github.com:gajus/eslint-plugin-flowtype
Browse files Browse the repository at this point in the history
  • Loading branch information
gajus committed Jul 9, 2018
2 parents 1f70db2 + e0c328d commit f37b1fc
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 17 deletions.
2 changes: 2 additions & 0 deletions .README/rules/no-mutable-array.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ General reasons for using immutable data structures:
* They always have failure atomicity
* They are much easier to cache

Note that initialization of a variable with an empty array is considered valid (e.g., `const values: Array<string> = [];`). This behavior resembles the behavior of Flow's [unsealed objects](https://flow.org/en/docs/types/objects/#toc-unsealed-objects), as it is assumed that empty array is intended to be mutated.

<!-- assertions noMutableArray -->
14 changes: 11 additions & 3 deletions src/rules/noFlowFixMeComments.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,24 @@ const create = (context) => {
};

return {
BlockComment: handleComment,
GenericTypeAnnotation: (node) => {
GenericTypeAnnotation (node) {
if (isIdentifier(node.id, /\$FlowFixMe/)) {
context.report({
message,
node: node.id
});
}
},
LineComment: handleComment

Program () {
context
.getSourceCode()
.getAllComments()
.filter((comment) => {
return comment.type === 'Block' || comment.type === 'Line';
})
.forEach(handleComment);
}
};
};

Expand Down
49 changes: 39 additions & 10 deletions src/rules/noMutableArray.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,49 @@
import _ from 'lodash';

const schema = [];

// const x = [];
const isEmptyArrayLiteral = (node) => {
return _.get(node, 'init.type') === 'ArrayExpression' && _.get(node, 'init.elements.length') === 0;
};

// const x = new Array(); const y = Array();
const isEmptyArrayInstance = (node) => {
if (_.get(node, 'init.type') === 'NewExpression' || _.get(node, 'init.type') === 'CallExpression') {
return _.get(node, 'init.callee.name') === 'Array' && _.get(node, 'init.arguments.length') === 0;
} else {
return false;
}
};

const isAnnotationOfEmptyArrayInit = (node) => {
if (_.has(node, 'parent.parent.parent')) {
const parent = _.get(node, 'parent.parent.parent');
const isVariableDeclaration = _.get(parent, 'type') === 'VariableDeclarator';

return isVariableDeclaration && (isEmptyArrayLiteral(parent) || isEmptyArrayInstance(parent));
} else {
return false;
}
};

const create = (context) => {
return {
ArrayTypeAnnotation (node) {
context.report({
fix (fixer) {
const rawElementType = context.getSourceCode().getText(node.elementType);

return fixer.replaceText(node, '$ReadOnlyArray<' + rawElementType + '>');
},
message: 'Use "$ReadOnlyArray" instead of array shorthand notation',
node
});
if (!isAnnotationOfEmptyArrayInit(node)) {
context.report({
fix (fixer) {
const rawElementType = context.getSourceCode().getText(node.elementType);

return fixer.replaceText(node, '$ReadOnlyArray<' + rawElementType + '>');
},
message: 'Use "$ReadOnlyArray" instead of array shorthand notation',
node
});
}
},
GenericTypeAnnotation (node) {
if (node.id.name === 'Array') {
if (node.id.name === 'Array' && !isAnnotationOfEmptyArrayInit(node)) {
context.report({
fix (fixer) {
return fixer.replaceText(node.id, '$ReadOnlyArray');
Expand Down
14 changes: 12 additions & 2 deletions src/rules/objectTypeDelimiter.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,18 @@ const create = (context) => {
}

const requireProperPunctuation = (node) => {
const tokens = context.getSourceCode().getTokens(node);
const lastToken = tokens[tokens.length - 1];
const sourceCode = context.getSourceCode();
const tokens = sourceCode.getTokens(node);
let lastToken;

lastToken = tokens[tokens.length - 1];
if (lastToken.type !== 'Punctuator' ||
!(lastToken.value === SEMICOLON.char ||
lastToken.value === COMMA.char)) {
const parentTokens = sourceCode.getTokens(node.parent);

lastToken = parentTokens[parentTokens.indexOf(lastToken) + 1];
}

if (lastToken.type === 'Punctuator') {
if (lastToken.value === BAD.char) {
Expand Down
14 changes: 12 additions & 2 deletions src/rules/sortKeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ const variances = {
plus: '+'
};

const getVariance = (node) => {
if (_.isString(node.variance)) {
return variances[node.variance] || '';
} else if (_.get(node, 'variance.type') === 'Variance') {
return variances[node.variance.kind] || '';
} else {
return '';
}
};

const generateOrderedList = (context, sort, properties) => {
return properties.map((property) => {
const name = getParameterName(property, context);
Expand All @@ -84,7 +94,7 @@ const generateOrderedList = (context, sort, properties) => {
value = context.getSourceCode().getText(property.value);
}

return [(variances[property.variance] || '') + name + (property.optional ? '?' : ''), value];
return [name, getVariance(property) + name + (property.optional ? '?' : ''), value];
})
.sort((first, second) => {
return sort(first[0], second[0]) ? -1 : 1;
Expand All @@ -94,7 +104,7 @@ const generateOrderedList = (context, sort, properties) => {
return item[0];
}

return item[0] + ': ' + item[1];
return item[1] + ': ' + item[2];
});
};

Expand Down
25 changes: 25 additions & 0 deletions tests/rules/assertions/noMutableArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,36 @@ export default {
code: 'type X = string[]',
errors: [{message: 'Use "$ReadOnlyArray" instead of array shorthand notation'}],
output: 'type X = $ReadOnlyArray<string>'
},
{
code: 'const values: Array<Array<string>> = [];',
errors: [{message: 'Use "$ReadOnlyArray" instead of "Array"'}],
output: 'const values: Array<$ReadOnlyArray<string>> = [];'
},
{
code: 'let values: Array<Array<string>>;',
errors: [
{message: 'Use "$ReadOnlyArray" instead of "Array"'},
{message: 'Use "$ReadOnlyArray" instead of "Array"'}
],
output: 'let values: $ReadOnlyArray<$ReadOnlyArray<string>>;'
}
],
valid: [
{
code: 'type X = $ReadOnlyArray<string>'
},
{
code: 'const values: Array<$ReadOnlyArray<string>> = [];'
},
{
code: 'const values: $ReadOnlyArray<string>[] = [];'
},
{
code: 'const values: Array<$ReadOnlyArray<string>> = new Array();'
},
{
code: 'const values: Array<$ReadOnlyArray<string>> = Array();'
}
]
};
40 changes: 40 additions & 0 deletions tests/rules/assertions/sortKeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,46 @@ export default {
},
}
`
},
{
code: `
type FooType = {
+c: number,
-b: number,
a: number,
}
`,
errors: [
{message: 'Expected type annotations to be in ascending order. "b" should be before "c".'},
{message: 'Expected type annotations to be in ascending order. "a" should be before "b".'}
],
output: `
type FooType = {
a: number,
-b: number,
+c: number,
}
`
},
{
code: `
type FooType = {|
+c: number,
-b: number,
a: number,
|}
`,
errors: [
{message: 'Expected type annotations to be in ascending order. "b" should be before "c".'},
{message: 'Expected type annotations to be in ascending order. "a" should be before "b".'}
],
output: `
type FooType = {|
a: number,
-b: number,
+c: number,
|}
`
}
/* eslint-enable no-restricted-syntax */
],
Expand Down

0 comments on commit f37b1fc

Please sign in to comment.