oxlint-plugin-react-native 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/index.js +14 -14
- package/dist/rules/no-color-literals.js +6 -6
- package/dist/rules/no-inline-styles.js +6 -6
- package/dist/rules/no-raw-text.js +24 -24
- package/dist/rules/no-single-element-style-arrays.js +7 -7
- package/dist/rules/no-unused-styles.js +18 -6
- package/dist/rules/sort-styles.js +17 -17
- package/dist/util/Components.js +16 -16
- package/dist/util/stylesheet.js +98 -57
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Lint rules for [React Native](https://reactnative.dev/) projects, built for [Oxlint](https://github.com/oxc-project/oxc).
|
|
4
4
|
|
|
5
|
+
Rules are based on [eslint-plugin-react-native](https://github.com/Intellicode/eslint-plugin-react-native) by Intellicode, ported to Oxlint. This plugin may evolve with new rules, improvements, or updates over time.
|
|
6
|
+
|
|
5
7
|
---
|
|
6
8
|
|
|
7
9
|
## Installation
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { eslintCompatPlugin } from
|
|
2
|
-
import noUnusedStyles from
|
|
3
|
-
import noInlineStyles from
|
|
4
|
-
import noColorLiterals from
|
|
5
|
-
import sortStyles from
|
|
6
|
-
import noRawText from
|
|
7
|
-
import noSingleElementStyleArrays from
|
|
1
|
+
import { eslintCompatPlugin } from "@oxlint/plugins";
|
|
2
|
+
import noUnusedStyles from "./rules/no-unused-styles.js";
|
|
3
|
+
import noInlineStyles from "./rules/no-inline-styles.js";
|
|
4
|
+
import noColorLiterals from "./rules/no-color-literals.js";
|
|
5
|
+
import sortStyles from "./rules/sort-styles.js";
|
|
6
|
+
import noRawText from "./rules/no-raw-text.js";
|
|
7
|
+
import noSingleElementStyleArrays from "./rules/no-single-element-style-arrays.js";
|
|
8
8
|
const allRules = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
"no-unused-styles": noUnusedStyles,
|
|
10
|
+
"no-inline-styles": noInlineStyles,
|
|
11
|
+
"no-color-literals": noColorLiterals,
|
|
12
|
+
"sort-styles": sortStyles,
|
|
13
|
+
"no-raw-text": noRawText,
|
|
14
|
+
"no-single-element-style-arrays": noSingleElementStyleArrays,
|
|
15
15
|
};
|
|
16
16
|
export default eslintCompatPlugin({
|
|
17
|
-
meta: { name:
|
|
17
|
+
meta: { name: "oxlint-plugin-react-native" },
|
|
18
18
|
rules: allRules,
|
|
19
19
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { detect } from
|
|
2
|
-
import { StyleSheets, astHelpers } from
|
|
3
|
-
import * as util from
|
|
1
|
+
import { detect } from "../util/Components.js";
|
|
2
|
+
import { StyleSheets, astHelpers } from "../util/stylesheet.js";
|
|
3
|
+
import * as util from "util";
|
|
4
4
|
const rule = detect((context) => {
|
|
5
5
|
let styleSheets;
|
|
6
6
|
return {
|
|
@@ -24,7 +24,7 @@ const rule = detect((context) => {
|
|
|
24
24
|
styleSheets.addColorLiterals(literals);
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
|
-
|
|
27
|
+
"Program:exit": () => {
|
|
28
28
|
const colorLiterals = styleSheets.getColorLiterals();
|
|
29
29
|
if (colorLiterals) {
|
|
30
30
|
colorLiterals.forEach((style) => {
|
|
@@ -32,7 +32,7 @@ const rule = detect((context) => {
|
|
|
32
32
|
const expression = util.inspect(style.expression);
|
|
33
33
|
context.report({
|
|
34
34
|
node: style.node,
|
|
35
|
-
message:
|
|
35
|
+
message: "Color literal: {{expression}}",
|
|
36
36
|
data: { expression },
|
|
37
37
|
});
|
|
38
38
|
}
|
|
@@ -45,5 +45,5 @@ export default {
|
|
|
45
45
|
meta: {
|
|
46
46
|
schema: [],
|
|
47
47
|
},
|
|
48
|
-
createOnce: rule
|
|
48
|
+
createOnce: rule,
|
|
49
49
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { detect } from
|
|
2
|
-
import { StyleSheets, astHelpers } from
|
|
3
|
-
import * as util from
|
|
1
|
+
import { detect } from "../util/Components.js";
|
|
2
|
+
import { StyleSheets, astHelpers } from "../util/stylesheet.js";
|
|
3
|
+
import * as util from "util";
|
|
4
4
|
const rule = detect((context) => {
|
|
5
5
|
// Setup state per-file (createOnce)
|
|
6
6
|
let styleSheets;
|
|
@@ -14,7 +14,7 @@ const rule = detect((context) => {
|
|
|
14
14
|
styleSheets.addObjectExpressions(styles);
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
|
-
|
|
17
|
+
"Program:exit": () => {
|
|
18
18
|
const inlineStyles = styleSheets.getObjectExpressions();
|
|
19
19
|
if (inlineStyles) {
|
|
20
20
|
inlineStyles.forEach((style) => {
|
|
@@ -22,7 +22,7 @@ const rule = detect((context) => {
|
|
|
22
22
|
const expression = util.inspect(style.expression);
|
|
23
23
|
context.report({
|
|
24
24
|
node: style.node,
|
|
25
|
-
message:
|
|
25
|
+
message: "Inline style: {{expression}}",
|
|
26
26
|
data: { expression },
|
|
27
27
|
});
|
|
28
28
|
}
|
|
@@ -35,5 +35,5 @@ export default {
|
|
|
35
35
|
meta: {
|
|
36
36
|
schema: [],
|
|
37
37
|
},
|
|
38
|
-
createOnce: rule
|
|
38
|
+
createOnce: rule,
|
|
39
39
|
};
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
const elementName = (node) => {
|
|
2
2
|
const reversedIdentifiers = [];
|
|
3
|
-
if (node.type ===
|
|
4
|
-
node.openingElement.type ===
|
|
3
|
+
if (node.type === "JSXElement" &&
|
|
4
|
+
node.openingElement.type === "JSXOpeningElement") {
|
|
5
5
|
let object = node.openingElement.name;
|
|
6
|
-
while (object.type ===
|
|
7
|
-
if (object.property.type ===
|
|
6
|
+
while (object.type === "JSXMemberExpression") {
|
|
7
|
+
if (object.property.type === "JSXIdentifier") {
|
|
8
8
|
reversedIdentifiers.push(object.property.name);
|
|
9
9
|
}
|
|
10
10
|
object = object.object;
|
|
11
11
|
}
|
|
12
|
-
if (object.type ===
|
|
12
|
+
if (object.type === "JSXIdentifier") {
|
|
13
13
|
reversedIdentifiers.push(object.name);
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
return reversedIdentifiers.reverse().join(
|
|
16
|
+
return reversedIdentifiers.reverse().join(".");
|
|
17
17
|
};
|
|
18
18
|
const hasAllowedParent = (parent, allowedElements) => {
|
|
19
19
|
let curNode = parent;
|
|
20
20
|
while (curNode) {
|
|
21
|
-
if (curNode.type ===
|
|
21
|
+
if (curNode.type === "JSXElement") {
|
|
22
22
|
const name = elementName(curNode);
|
|
23
23
|
if (allowedElements.includes(name)) {
|
|
24
24
|
return true;
|
|
@@ -36,50 +36,50 @@ const rule = (context) => {
|
|
|
36
36
|
const options = context.options[0] || {};
|
|
37
37
|
const skippedElements = options.skip ? options.skip : [];
|
|
38
38
|
_allowedElements = [
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
"Text",
|
|
40
|
+
"TSpan",
|
|
41
|
+
"StyledText",
|
|
42
|
+
"Animated.Text",
|
|
43
43
|
].concat(skippedElements);
|
|
44
44
|
}
|
|
45
45
|
return _allowedElements;
|
|
46
46
|
}
|
|
47
47
|
const report = (node) => {
|
|
48
|
-
const errorValue = node.type ===
|
|
48
|
+
const errorValue = node.type === "TemplateLiteral"
|
|
49
49
|
? `TemplateLiteral: ${node.expressions[0].name}`
|
|
50
50
|
: node.value.trim();
|
|
51
|
-
const formattedErrorValue = errorValue.length > 0 ? `Raw text (${errorValue})` :
|
|
51
|
+
const formattedErrorValue = errorValue.length > 0 ? `Raw text (${errorValue})` : "Whitespace(s)";
|
|
52
52
|
context.report({
|
|
53
53
|
node,
|
|
54
54
|
message: `${formattedErrorValue} cannot be used outside of a <Text> tag`,
|
|
55
55
|
});
|
|
56
56
|
};
|
|
57
|
-
const hasOnlyLineBreak = (value) => /^[\r\n\t\f\v]+$/.test(value.replace(/ /g,
|
|
57
|
+
const hasOnlyLineBreak = (value) => /^[\r\n\t\f\v]+$/.test(value.replace(/ /g, ""));
|
|
58
58
|
const getValidation = (node) => !hasAllowedParent(node.parent, getAllowedElements());
|
|
59
59
|
return {
|
|
60
60
|
Literal(node) {
|
|
61
61
|
const parentType = node.parent.type;
|
|
62
|
-
const onlyFor = [
|
|
63
|
-
if (typeof node.value !==
|
|
62
|
+
const onlyFor = ["JSXExpressionContainer", "JSXElement"];
|
|
63
|
+
if (typeof node.value !== "string" ||
|
|
64
64
|
hasOnlyLineBreak(node.value) ||
|
|
65
65
|
!onlyFor.includes(parentType) ||
|
|
66
|
-
(node.parent.parent && node.parent.parent.type ===
|
|
66
|
+
(node.parent.parent && node.parent.parent.type === "JSXAttribute"))
|
|
67
67
|
return;
|
|
68
|
-
const isStringLiteral = parentType ===
|
|
68
|
+
const isStringLiteral = parentType === "JSXExpressionContainer";
|
|
69
69
|
if (getValidation(isStringLiteral ? node.parent : node)) {
|
|
70
70
|
report(node);
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
73
|
JSXText(node) {
|
|
74
|
-
if (typeof node.value !==
|
|
74
|
+
if (typeof node.value !== "string" || hasOnlyLineBreak(node.value))
|
|
75
75
|
return;
|
|
76
76
|
if (getValidation(node)) {
|
|
77
77
|
report(node);
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
80
|
TemplateLiteral(node) {
|
|
81
|
-
if (node.parent.type !==
|
|
82
|
-
(node.parent.parent && node.parent.parent.type ===
|
|
81
|
+
if (node.parent.type !== "JSXExpressionContainer" ||
|
|
82
|
+
(node.parent.parent && node.parent.parent.type === "JSXAttribute"))
|
|
83
83
|
return;
|
|
84
84
|
if (getValidation(node.parent)) {
|
|
85
85
|
report(node);
|
|
@@ -91,12 +91,12 @@ export default {
|
|
|
91
91
|
meta: {
|
|
92
92
|
schema: [
|
|
93
93
|
{
|
|
94
|
-
type:
|
|
94
|
+
type: "object",
|
|
95
95
|
properties: {
|
|
96
96
|
skip: {
|
|
97
|
-
type:
|
|
97
|
+
type: "array",
|
|
98
98
|
items: {
|
|
99
|
-
type:
|
|
99
|
+
type: "string",
|
|
100
100
|
},
|
|
101
101
|
},
|
|
102
102
|
},
|
|
@@ -2,7 +2,7 @@ const rule = (context) => {
|
|
|
2
2
|
function reportNode(JSXExpressionNode) {
|
|
3
3
|
context.report({
|
|
4
4
|
node: JSXExpressionNode,
|
|
5
|
-
message:
|
|
5
|
+
message: "Single element style arrays are not necessary and cause unnecessary re-renders",
|
|
6
6
|
fix(fixer) {
|
|
7
7
|
const realStyleNode = JSXExpressionNode.value.expression.elements[0];
|
|
8
8
|
const styleSource = context.sourceCode.getText(realStyleNode);
|
|
@@ -15,11 +15,11 @@ const rule = (context) => {
|
|
|
15
15
|
// --------------------------------------------------------------------------
|
|
16
16
|
return {
|
|
17
17
|
JSXAttribute(node) {
|
|
18
|
-
if (node.name.name !==
|
|
18
|
+
if (node.name.name !== "style")
|
|
19
19
|
return;
|
|
20
20
|
if (!node.value.expression)
|
|
21
21
|
return;
|
|
22
|
-
if (node.value.expression.type !==
|
|
22
|
+
if (node.value.expression.type !== "ArrayExpression")
|
|
23
23
|
return;
|
|
24
24
|
if (node.value.expression.elements.length === 1) {
|
|
25
25
|
reportNode(node);
|
|
@@ -30,12 +30,12 @@ const rule = (context) => {
|
|
|
30
30
|
export default {
|
|
31
31
|
meta: {
|
|
32
32
|
docs: {
|
|
33
|
-
description:
|
|
34
|
-
category:
|
|
33
|
+
description: "Disallow single element style arrays. These cause unnecessary re-renders as the identity of the array always changes",
|
|
34
|
+
category: "Stylistic Issues",
|
|
35
35
|
recommended: false,
|
|
36
|
-
url:
|
|
36
|
+
url: "",
|
|
37
37
|
},
|
|
38
|
-
fixable:
|
|
38
|
+
fixable: "code",
|
|
39
39
|
},
|
|
40
40
|
createOnce: rule,
|
|
41
41
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { detect } from
|
|
2
|
-
import { StyleSheets, astHelpers } from
|
|
1
|
+
import { detect } from "../util/Components.js";
|
|
2
|
+
import { StyleSheets, astHelpers } from "../util/stylesheet.js";
|
|
3
3
|
const rule = detect((context, components) => {
|
|
4
4
|
let styleSheets;
|
|
5
5
|
let styleReferences;
|
|
@@ -9,11 +9,11 @@ const rule = detect((context, components) => {
|
|
|
9
9
|
const styles = unusedStyles[key];
|
|
10
10
|
styles.forEach((node) => {
|
|
11
11
|
const message = [
|
|
12
|
-
|
|
12
|
+
"Unused style detected: ",
|
|
13
13
|
key,
|
|
14
|
-
|
|
14
|
+
".",
|
|
15
15
|
node.key.name,
|
|
16
|
-
].join(
|
|
16
|
+
].join("");
|
|
17
17
|
context.report({ node, message });
|
|
18
18
|
});
|
|
19
19
|
}
|
|
@@ -36,10 +36,22 @@ const rule = detect((context, components) => {
|
|
|
36
36
|
const styles = astHelpers.getStyleDeclarations(node);
|
|
37
37
|
if (styleSheetName) {
|
|
38
38
|
styleSheets.add(styleSheetName, styles);
|
|
39
|
+
if (astHelpers.isStyleSheetExported(node)) {
|
|
40
|
+
styleSheets.markAsExported(styleSheetName);
|
|
41
|
+
}
|
|
39
42
|
}
|
|
40
43
|
}
|
|
41
44
|
},
|
|
42
|
-
|
|
45
|
+
ExportNamedDeclaration: function (node) {
|
|
46
|
+
if (node.specifiers) {
|
|
47
|
+
for (const spec of node.specifiers) {
|
|
48
|
+
const name = spec.local && spec.local.name;
|
|
49
|
+
if (name)
|
|
50
|
+
styleSheets.markAsExported(name);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"Program:exit": function () {
|
|
43
55
|
const list = components.all();
|
|
44
56
|
if (Object.keys(list).length > 0) {
|
|
45
57
|
styleReferences.forEach((reference) => {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { astHelpers } from
|
|
1
|
+
import { astHelpers } from "../util/stylesheet.js";
|
|
2
2
|
const rule = (context) => {
|
|
3
3
|
// Defer context.options and context.sourceCode to visitor (oxlint forbids in createOnce).
|
|
4
4
|
function sort(array, order) {
|
|
5
5
|
return [...array].sort((a, b) => {
|
|
6
|
-
const identifierA = astHelpers.getStylePropertyIdentifier(a) ||
|
|
7
|
-
const identifierB = astHelpers.getStylePropertyIdentifier(b) ||
|
|
6
|
+
const identifierA = astHelpers.getStylePropertyIdentifier(a) || "";
|
|
7
|
+
const identifierB = astHelpers.getStylePropertyIdentifier(b) || "";
|
|
8
8
|
let sortOrder = 0;
|
|
9
9
|
if (astHelpers.isEitherShortHand(identifierA, identifierB)) {
|
|
10
10
|
return a.range[0] - b.range[0];
|
|
@@ -15,7 +15,7 @@ const rule = (context) => {
|
|
|
15
15
|
else if (identifierA > identifierB) {
|
|
16
16
|
sortOrder = 1;
|
|
17
17
|
}
|
|
18
|
-
return sortOrder * (order ===
|
|
18
|
+
return sortOrder * (order === "asc" ? 1 : -1);
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
21
|
function report(array, type, node, prev, current, order, sourceCode) {
|
|
@@ -50,12 +50,12 @@ const rule = (context) => {
|
|
|
50
50
|
for (let i = 1; i < array.length; i += 1) {
|
|
51
51
|
const previous = array[i - 1];
|
|
52
52
|
const current = array[i];
|
|
53
|
-
if (previous.type !==
|
|
53
|
+
if (previous.type !== "Property" || current.type !== "Property") {
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
|
-
const prevName = astHelpers.getStylePropertyIdentifier(previous) ||
|
|
57
|
-
const currentName = astHelpers.getStylePropertyIdentifier(current) ||
|
|
58
|
-
const oneIsShorthandForTheOther = arrayName ===
|
|
56
|
+
const prevName = astHelpers.getStylePropertyIdentifier(previous) || "";
|
|
57
|
+
const currentName = astHelpers.getStylePropertyIdentifier(current) || "";
|
|
58
|
+
const oneIsShorthandForTheOther = arrayName === "style properties" &&
|
|
59
59
|
astHelpers.isEitherShortHand(prevName, currentName);
|
|
60
60
|
if (!oneIsShorthandForTheOther && !isValidOrder(prevName, currentName)) {
|
|
61
61
|
return report(array, arrayName, node, previous, current, order, sourceCode);
|
|
@@ -64,11 +64,11 @@ const rule = (context) => {
|
|
|
64
64
|
}
|
|
65
65
|
return {
|
|
66
66
|
CallExpression: function (node) {
|
|
67
|
-
const order = context.options[0] ||
|
|
67
|
+
const order = context.options[0] || "asc";
|
|
68
68
|
const options = context.options[1] || {};
|
|
69
69
|
const { ignoreClassNames, ignoreStyleProperties } = options;
|
|
70
70
|
const sourceCode = context.sourceCode;
|
|
71
|
-
const isValidOrder = order ===
|
|
71
|
+
const isValidOrder = order === "asc"
|
|
72
72
|
? (a, b) => a <= b
|
|
73
73
|
: (a, b) => a >= b;
|
|
74
74
|
if (!astHelpers.isStyleSheetDeclaration(node, context.settings)) {
|
|
@@ -77,7 +77,7 @@ const rule = (context) => {
|
|
|
77
77
|
const classDefinitionsChunks = astHelpers.getStyleDeclarationsChunks(node);
|
|
78
78
|
if (!ignoreClassNames) {
|
|
79
79
|
classDefinitionsChunks.forEach((classDefinitions) => {
|
|
80
|
-
checkIsSorted(classDefinitions,
|
|
80
|
+
checkIsSorted(classDefinitions, "class names", node, order, options, sourceCode, isValidOrder);
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
if (ignoreStyleProperties)
|
|
@@ -90,7 +90,7 @@ const rule = (context) => {
|
|
|
90
90
|
}
|
|
91
91
|
const stylePropertyChunks = astHelpers.getPropertiesChunks(styleProperties);
|
|
92
92
|
stylePropertyChunks.forEach((stylePropertyChunk) => {
|
|
93
|
-
checkIsSorted(stylePropertyChunk,
|
|
93
|
+
checkIsSorted(stylePropertyChunk, "style properties", node, order, options, sourceCode, isValidOrder);
|
|
94
94
|
});
|
|
95
95
|
});
|
|
96
96
|
});
|
|
@@ -99,19 +99,19 @@ const rule = (context) => {
|
|
|
99
99
|
};
|
|
100
100
|
export default {
|
|
101
101
|
meta: {
|
|
102
|
-
fixable:
|
|
102
|
+
fixable: "code",
|
|
103
103
|
schema: [
|
|
104
104
|
{
|
|
105
|
-
enum: [
|
|
105
|
+
enum: ["asc", "desc"],
|
|
106
106
|
},
|
|
107
107
|
{
|
|
108
|
-
type:
|
|
108
|
+
type: "object",
|
|
109
109
|
properties: {
|
|
110
110
|
ignoreClassNames: {
|
|
111
|
-
type:
|
|
111
|
+
type: "boolean",
|
|
112
112
|
},
|
|
113
113
|
ignoreStyleProperties: {
|
|
114
|
-
type:
|
|
114
|
+
type: "boolean",
|
|
115
115
|
},
|
|
116
116
|
},
|
|
117
117
|
additionalProperties: false,
|
package/dist/util/Components.js
CHANGED
|
@@ -3,7 +3,7 @@ export class Components {
|
|
|
3
3
|
this.list = {};
|
|
4
4
|
}
|
|
5
5
|
getId(node) {
|
|
6
|
-
return node ? node.range.join(
|
|
6
|
+
return node ? node.range.join(":") : "";
|
|
7
7
|
}
|
|
8
8
|
add(node, confidence) {
|
|
9
9
|
const id = this.getId(node);
|
|
@@ -76,22 +76,22 @@ export function componentRule(rule, context) {
|
|
|
76
76
|
isReturningJSX: function (node) {
|
|
77
77
|
let property;
|
|
78
78
|
switch (node.type) {
|
|
79
|
-
case
|
|
80
|
-
property =
|
|
79
|
+
case "ReturnStatement":
|
|
80
|
+
property = "argument";
|
|
81
81
|
break;
|
|
82
|
-
case
|
|
83
|
-
property =
|
|
82
|
+
case "ArrowFunctionExpression":
|
|
83
|
+
property = "body";
|
|
84
84
|
break;
|
|
85
85
|
default:
|
|
86
86
|
return false;
|
|
87
87
|
}
|
|
88
88
|
const returnsJSX = node[property] &&
|
|
89
|
-
(node[property].type ===
|
|
90
|
-
node[property].type ===
|
|
89
|
+
(node[property].type === "JSXElement" ||
|
|
90
|
+
node[property].type === "JSXFragment");
|
|
91
91
|
const returnsReactCreateElement = node[property] &&
|
|
92
92
|
node[property].callee &&
|
|
93
93
|
node[property].callee.property &&
|
|
94
|
-
node[property].callee.property.name ===
|
|
94
|
+
node[property].callee.property.name === "createElement";
|
|
95
95
|
return Boolean(returnsJSX || returnsReactCreateElement);
|
|
96
96
|
},
|
|
97
97
|
getParentComponent: function (_n) {
|
|
@@ -112,7 +112,7 @@ export function componentRule(rule, context) {
|
|
|
112
112
|
},
|
|
113
113
|
getParentES6Component: function (_n) {
|
|
114
114
|
let scope = (context.sourceCode || context).getScope(_n);
|
|
115
|
-
while (scope && scope.type !==
|
|
115
|
+
while (scope && scope.type !== "class") {
|
|
116
116
|
scope = scope.upper;
|
|
117
117
|
}
|
|
118
118
|
const node = scope && scope.block;
|
|
@@ -126,8 +126,8 @@ export function componentRule(rule, context) {
|
|
|
126
126
|
while (scope) {
|
|
127
127
|
const node = scope.block;
|
|
128
128
|
const isFunction = /Function/.test(node.type);
|
|
129
|
-
const isNotMethod = !node.parent || node.parent.type !==
|
|
130
|
-
const isNotArgument = !node.parent || node.parent.type !==
|
|
129
|
+
const isNotMethod = !node.parent || node.parent.type !== "MethodDefinition";
|
|
130
|
+
const isNotArgument = !node.parent || node.parent.type !== "CallExpression";
|
|
131
131
|
if (isFunction && isNotMethod && isNotArgument) {
|
|
132
132
|
return node;
|
|
133
133
|
}
|
|
@@ -144,10 +144,10 @@ export function componentRule(rule, context) {
|
|
|
144
144
|
const componentPath = [];
|
|
145
145
|
while (currentNode) {
|
|
146
146
|
if (currentNode.property &&
|
|
147
|
-
currentNode.property.type ===
|
|
147
|
+
currentNode.property.type === "Identifier") {
|
|
148
148
|
componentPath.push(currentNode.property.name);
|
|
149
149
|
}
|
|
150
|
-
if (currentNode.object && currentNode.object.type ===
|
|
150
|
+
if (currentNode.object && currentNode.object.type === "Identifier") {
|
|
151
151
|
componentPath.push(currentNode.object.name);
|
|
152
152
|
}
|
|
153
153
|
currentNode = currentNode.object;
|
|
@@ -171,9 +171,9 @@ export function componentRule(rule, context) {
|
|
|
171
171
|
let defInScope;
|
|
172
172
|
const { defs } = variableInScope;
|
|
173
173
|
for (i = 0, j = defs.length; i < j; i++) {
|
|
174
|
-
if (defs[i].type ===
|
|
175
|
-
defs[i].type ===
|
|
176
|
-
defs[i].type ===
|
|
174
|
+
if (defs[i].type === "ClassName" ||
|
|
175
|
+
defs[i].type === "FunctionName" ||
|
|
176
|
+
defs[i].type === "Variable") {
|
|
177
177
|
defInScope = defs[i];
|
|
178
178
|
break;
|
|
179
179
|
}
|
package/dist/util/stylesheet.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export class StyleSheets {
|
|
2
2
|
constructor() {
|
|
3
|
+
this.exportedNames = new Set();
|
|
3
4
|
this.styleSheets = {};
|
|
4
5
|
this.colorLiterals = [];
|
|
5
6
|
this.objectExpressions = [];
|
|
@@ -7,8 +8,11 @@ export class StyleSheets {
|
|
|
7
8
|
add(styleSheetName, properties) {
|
|
8
9
|
this.styleSheets[styleSheetName] = properties;
|
|
9
10
|
}
|
|
11
|
+
markAsExported(styleSheetName) {
|
|
12
|
+
this.exportedNames.add(styleSheetName);
|
|
13
|
+
}
|
|
10
14
|
markAsUsed(fullyQualifiedName) {
|
|
11
|
-
const nameSplit = fullyQualifiedName.split(
|
|
15
|
+
const nameSplit = fullyQualifiedName.split(".");
|
|
12
16
|
const styleSheetName = nameSplit[0];
|
|
13
17
|
const styleSheetProperty = nameSplit[1];
|
|
14
18
|
if (this.styleSheets[styleSheetName]) {
|
|
@@ -16,7 +20,13 @@ export class StyleSheets {
|
|
|
16
20
|
}
|
|
17
21
|
}
|
|
18
22
|
getUnusedReferences() {
|
|
19
|
-
|
|
23
|
+
const result = {};
|
|
24
|
+
for (const [name, properties] of Object.entries(this.styleSheets)) {
|
|
25
|
+
if (properties.length > 0 && !this.exportedNames.has(name)) {
|
|
26
|
+
result[name] = properties;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
20
30
|
}
|
|
21
31
|
addColorLiterals(expressions) {
|
|
22
32
|
this.colorLiterals = this.colorLiterals.concat(expressions);
|
|
@@ -33,11 +43,11 @@ export class StyleSheets {
|
|
|
33
43
|
}
|
|
34
44
|
let currentContent;
|
|
35
45
|
const getSourceCode = (node) => currentContent.sourceCode.getText(node);
|
|
36
|
-
const getStyleSheetObjectNames = (settings) => settings[
|
|
46
|
+
const getStyleSheetObjectNames = (settings) => settings["react-native/style-sheet-object-names"] || ["StyleSheet"];
|
|
37
47
|
export const astHelpers = {
|
|
38
48
|
containsStyleSheetObject: function (node, objectNames) {
|
|
39
49
|
return Boolean(node &&
|
|
40
|
-
node.type ===
|
|
50
|
+
node.type === "CallExpression" &&
|
|
41
51
|
node.callee &&
|
|
42
52
|
node.callee.object &&
|
|
43
53
|
node.callee.object.name &&
|
|
@@ -47,7 +57,7 @@ export const astHelpers = {
|
|
|
47
57
|
return Boolean(node &&
|
|
48
58
|
node.callee &&
|
|
49
59
|
node.callee.property &&
|
|
50
|
-
node.callee.property.name ===
|
|
60
|
+
node.callee.property.name === "create");
|
|
51
61
|
},
|
|
52
62
|
isStyleSheetDeclaration: function (node, settings) {
|
|
53
63
|
const objectNames = getStyleSheetObjectNames(settings);
|
|
@@ -59,19 +69,40 @@ export const astHelpers = {
|
|
|
59
69
|
return node.parent.id.name;
|
|
60
70
|
}
|
|
61
71
|
},
|
|
72
|
+
/**
|
|
73
|
+
* Returns true if the StyleSheet.create call is part of an export, so its
|
|
74
|
+
* styles may be used in other files and should not be reported as unused.
|
|
75
|
+
*/
|
|
76
|
+
isStyleSheetExported: function (node) {
|
|
77
|
+
if (!node)
|
|
78
|
+
return false;
|
|
79
|
+
// export default StyleSheet.create(...)
|
|
80
|
+
if (node.parent && node.parent.type === "ExportDefaultDeclaration")
|
|
81
|
+
return true;
|
|
82
|
+
const declarator = node.parent;
|
|
83
|
+
if (!declarator || declarator.type !== "VariableDeclarator")
|
|
84
|
+
return false;
|
|
85
|
+
const declaration = declarator.parent;
|
|
86
|
+
if (!declaration)
|
|
87
|
+
return false;
|
|
88
|
+
// export const styles = StyleSheet.create(...)
|
|
89
|
+
if (declaration.parent && declaration.parent.type === "ExportNamedDeclaration")
|
|
90
|
+
return true;
|
|
91
|
+
return false;
|
|
92
|
+
},
|
|
62
93
|
getStyleDeclarations: function (node) {
|
|
63
94
|
if (node &&
|
|
64
|
-
node.type ===
|
|
95
|
+
node.type === "CallExpression" &&
|
|
65
96
|
node.arguments &&
|
|
66
97
|
node.arguments[0] &&
|
|
67
98
|
node.arguments[0].properties) {
|
|
68
|
-
return node.arguments[0].properties.filter((property) => property.type ===
|
|
99
|
+
return node.arguments[0].properties.filter((property) => property.type === "Property");
|
|
69
100
|
}
|
|
70
101
|
return [];
|
|
71
102
|
},
|
|
72
103
|
getStyleDeclarationsChunks: function (node) {
|
|
73
104
|
if (node &&
|
|
74
|
-
node.type ===
|
|
105
|
+
node.type === "CallExpression" &&
|
|
75
106
|
node.arguments &&
|
|
76
107
|
node.arguments[0] &&
|
|
77
108
|
node.arguments[0].properties) {
|
|
@@ -80,7 +111,7 @@ export const astHelpers = {
|
|
|
80
111
|
let chunk = [];
|
|
81
112
|
for (let i = 0; i < properties.length; i += 1) {
|
|
82
113
|
const property = properties[i];
|
|
83
|
-
if (property.type ===
|
|
114
|
+
if (property.type === "Property") {
|
|
84
115
|
chunk.push(property);
|
|
85
116
|
}
|
|
86
117
|
else if (chunk.length) {
|
|
@@ -100,7 +131,7 @@ export const astHelpers = {
|
|
|
100
131
|
let chunk = [];
|
|
101
132
|
for (let i = 0; i < properties.length; i += 1) {
|
|
102
133
|
const property = properties[i];
|
|
103
|
-
if (property.type ===
|
|
134
|
+
if (property.type === "Property") {
|
|
104
135
|
chunk.push(property);
|
|
105
136
|
}
|
|
106
137
|
else if (chunk.length) {
|
|
@@ -116,19 +147,19 @@ export const astHelpers = {
|
|
|
116
147
|
getExpressionIdentifier: function (node) {
|
|
117
148
|
if (node) {
|
|
118
149
|
switch (node.type) {
|
|
119
|
-
case
|
|
150
|
+
case "Identifier":
|
|
120
151
|
return node.name;
|
|
121
|
-
case
|
|
152
|
+
case "Literal":
|
|
122
153
|
return node.value;
|
|
123
|
-
case
|
|
154
|
+
case "TemplateLiteral":
|
|
124
155
|
return node.quasis.reduce((result, quasi, index) => result +
|
|
125
156
|
quasi.value.cooked +
|
|
126
|
-
astHelpers.getExpressionIdentifier(node.expressions[index]),
|
|
157
|
+
astHelpers.getExpressionIdentifier(node.expressions[index]), "");
|
|
127
158
|
default:
|
|
128
|
-
return
|
|
159
|
+
return "";
|
|
129
160
|
}
|
|
130
161
|
}
|
|
131
|
-
return
|
|
162
|
+
return "";
|
|
132
163
|
},
|
|
133
164
|
getStylePropertyIdentifier: function (node) {
|
|
134
165
|
if (node && node.key) {
|
|
@@ -136,10 +167,10 @@ export const astHelpers = {
|
|
|
136
167
|
}
|
|
137
168
|
},
|
|
138
169
|
isStyleAttribute: function (node) {
|
|
139
|
-
return Boolean(node.type ===
|
|
170
|
+
return Boolean(node.type === "JSXAttribute" &&
|
|
140
171
|
node.name &&
|
|
141
172
|
node.name.name &&
|
|
142
|
-
node.name.name.toLowerCase().includes(
|
|
173
|
+
node.name.name.toLowerCase().includes("style"));
|
|
143
174
|
},
|
|
144
175
|
collectStyleObjectExpressions: function (node, context) {
|
|
145
176
|
currentContent = context;
|
|
@@ -161,7 +192,7 @@ export const astHelpers = {
|
|
|
161
192
|
const styleReferenceContainers = node.expression.elements;
|
|
162
193
|
return astHelpers.collectColorLiteralsFromContainers(styleReferenceContainers);
|
|
163
194
|
}
|
|
164
|
-
if (node.type ===
|
|
195
|
+
if (node.type === "ObjectExpression") {
|
|
165
196
|
return astHelpers.getColorLiteralsFromNode(node);
|
|
166
197
|
}
|
|
167
198
|
return astHelpers.getColorLiteralsFromNode(node.expression);
|
|
@@ -188,17 +219,21 @@ export const astHelpers = {
|
|
|
188
219
|
return [];
|
|
189
220
|
}
|
|
190
221
|
switch (node.type) {
|
|
191
|
-
case
|
|
222
|
+
case "MemberExpression":
|
|
192
223
|
styleReference = astHelpers.getStyleReferenceFromExpression(node);
|
|
193
224
|
return [styleReference];
|
|
194
|
-
case
|
|
225
|
+
case "LogicalExpression":
|
|
195
226
|
leftStyleReferences = astHelpers.getStyleReferenceFromNode(node.left);
|
|
196
227
|
rightStyleReferences = astHelpers.getStyleReferenceFromNode(node.right);
|
|
197
|
-
return []
|
|
198
|
-
|
|
228
|
+
return []
|
|
229
|
+
.concat(leftStyleReferences)
|
|
230
|
+
.concat(rightStyleReferences);
|
|
231
|
+
case "ConditionalExpression":
|
|
199
232
|
leftStyleReferences = astHelpers.getStyleReferenceFromNode(node.consequent);
|
|
200
233
|
rightStyleReferences = astHelpers.getStyleReferenceFromNode(node.alternate);
|
|
201
|
-
return []
|
|
234
|
+
return []
|
|
235
|
+
.concat(leftStyleReferences)
|
|
236
|
+
.concat(rightStyleReferences);
|
|
202
237
|
default:
|
|
203
238
|
return [];
|
|
204
239
|
}
|
|
@@ -209,19 +244,21 @@ export const astHelpers = {
|
|
|
209
244
|
if (!node) {
|
|
210
245
|
return [];
|
|
211
246
|
}
|
|
212
|
-
if (node.type ===
|
|
247
|
+
if (node.type === "ObjectExpression") {
|
|
213
248
|
return [astHelpers.getStyleObjectFromExpression(node)];
|
|
214
249
|
}
|
|
215
250
|
switch (node.type) {
|
|
216
|
-
case
|
|
251
|
+
case "LogicalExpression":
|
|
217
252
|
leftStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.left);
|
|
218
|
-
rightStyleObjectExpression =
|
|
253
|
+
rightStyleObjectExpression =
|
|
254
|
+
astHelpers.getStyleObjectExpressionFromNode(node.right);
|
|
219
255
|
return []
|
|
220
256
|
.concat(leftStyleObjectExpression)
|
|
221
257
|
.concat(rightStyleObjectExpression);
|
|
222
|
-
case
|
|
258
|
+
case "ConditionalExpression":
|
|
223
259
|
leftStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.consequent);
|
|
224
|
-
rightStyleObjectExpression =
|
|
260
|
+
rightStyleObjectExpression =
|
|
261
|
+
astHelpers.getStyleObjectExpressionFromNode(node.alternate);
|
|
225
262
|
return []
|
|
226
263
|
.concat(leftStyleObjectExpression)
|
|
227
264
|
.concat(rightStyleObjectExpression);
|
|
@@ -235,27 +272,31 @@ export const astHelpers = {
|
|
|
235
272
|
if (!node) {
|
|
236
273
|
return [];
|
|
237
274
|
}
|
|
238
|
-
if (node.type ===
|
|
275
|
+
if (node.type === "ObjectExpression") {
|
|
239
276
|
return [astHelpers.getColorLiteralsFromExpression(node)];
|
|
240
277
|
}
|
|
241
278
|
switch (node.type) {
|
|
242
|
-
case
|
|
279
|
+
case "LogicalExpression":
|
|
243
280
|
leftColorLiterals = astHelpers.getColorLiteralsFromNode(node.left);
|
|
244
281
|
rightColorLiterals = astHelpers.getColorLiteralsFromNode(node.right);
|
|
245
|
-
return []
|
|
246
|
-
|
|
282
|
+
return []
|
|
283
|
+
.concat(leftColorLiterals)
|
|
284
|
+
.concat(rightColorLiterals);
|
|
285
|
+
case "ConditionalExpression":
|
|
247
286
|
leftColorLiterals = astHelpers.getColorLiteralsFromNode(node.consequent);
|
|
248
287
|
rightColorLiterals = astHelpers.getColorLiteralsFromNode(node.alternate);
|
|
249
|
-
return []
|
|
288
|
+
return []
|
|
289
|
+
.concat(leftColorLiterals)
|
|
290
|
+
.concat(rightColorLiterals);
|
|
250
291
|
default:
|
|
251
292
|
return [];
|
|
252
293
|
}
|
|
253
294
|
},
|
|
254
295
|
hasArrayOfStyleReferences: function (node) {
|
|
255
296
|
return (node &&
|
|
256
|
-
Boolean(node.type ===
|
|
297
|
+
Boolean(node.type === "JSXExpressionContainer" &&
|
|
257
298
|
node.expression &&
|
|
258
|
-
node.expression.type ===
|
|
299
|
+
node.expression.type === "ArrayExpression"));
|
|
259
300
|
},
|
|
260
301
|
getStyleReferenceFromExpression: function (node) {
|
|
261
302
|
const result = [];
|
|
@@ -267,7 +308,7 @@ export const astHelpers = {
|
|
|
267
308
|
if (property) {
|
|
268
309
|
result.push(property);
|
|
269
310
|
}
|
|
270
|
-
return result.join(
|
|
311
|
+
return result.join(".");
|
|
271
312
|
},
|
|
272
313
|
getStyleObjectFromExpression: function (node) {
|
|
273
314
|
const obj = {};
|
|
@@ -277,27 +318,27 @@ export const astHelpers = {
|
|
|
277
318
|
if (!p.value || !p.key) {
|
|
278
319
|
return;
|
|
279
320
|
}
|
|
280
|
-
if (p.value.type ===
|
|
321
|
+
if (p.value.type === "Literal") {
|
|
281
322
|
invalid = true;
|
|
282
323
|
obj[p.key.name] = p.value.value;
|
|
283
324
|
}
|
|
284
|
-
else if (p.value.type ===
|
|
325
|
+
else if (p.value.type === "ConditionalExpression") {
|
|
285
326
|
const innerNode = p.value;
|
|
286
|
-
if (innerNode.consequent.type ===
|
|
287
|
-
innerNode.alternate.type ===
|
|
327
|
+
if (innerNode.consequent.type === "Literal" ||
|
|
328
|
+
innerNode.alternate.type === "Literal") {
|
|
288
329
|
invalid = true;
|
|
289
330
|
obj[p.key.name] = getSourceCode(innerNode);
|
|
290
331
|
}
|
|
291
332
|
}
|
|
292
|
-
else if (p.value.type ===
|
|
293
|
-
p.value.operator ===
|
|
294
|
-
p.value.argument.type ===
|
|
333
|
+
else if (p.value.type === "UnaryExpression" &&
|
|
334
|
+
p.value.operator === "-" &&
|
|
335
|
+
p.value.argument.type === "Literal") {
|
|
295
336
|
invalid = true;
|
|
296
337
|
obj[p.key.name] = -1 * p.value.argument.value;
|
|
297
338
|
}
|
|
298
|
-
else if (p.value.type ===
|
|
299
|
-
p.value.operator ===
|
|
300
|
-
p.value.argument.type ===
|
|
339
|
+
else if (p.value.type === "UnaryExpression" &&
|
|
340
|
+
p.value.operator === "+" &&
|
|
341
|
+
p.value.argument.type === "Literal") {
|
|
301
342
|
invalid = true;
|
|
302
343
|
obj[p.key.name] = p.value.argument.value;
|
|
303
344
|
}
|
|
@@ -312,15 +353,15 @@ export const astHelpers = {
|
|
|
312
353
|
node.properties.forEach((p) => {
|
|
313
354
|
if (p.key &&
|
|
314
355
|
p.key.name &&
|
|
315
|
-
p.key.name.toLowerCase().indexOf(
|
|
316
|
-
if (p.value.type ===
|
|
356
|
+
p.key.name.toLowerCase().indexOf("color") !== -1) {
|
|
357
|
+
if (p.value.type === "Literal") {
|
|
317
358
|
invalid = true;
|
|
318
359
|
obj[p.key.name] = p.value.value;
|
|
319
360
|
}
|
|
320
|
-
else if (p.value.type ===
|
|
361
|
+
else if (p.value.type === "ConditionalExpression") {
|
|
321
362
|
const innerNode = p.value;
|
|
322
|
-
if (innerNode.consequent.type ===
|
|
323
|
-
innerNode.alternate.type ===
|
|
363
|
+
if (innerNode.consequent.type === "Literal" ||
|
|
364
|
+
innerNode.alternate.type === "Literal") {
|
|
324
365
|
invalid = true;
|
|
325
366
|
obj[p.key.name] = getSourceCode(innerNode);
|
|
326
367
|
}
|
|
@@ -343,17 +384,17 @@ export const astHelpers = {
|
|
|
343
384
|
getPotentialStyleReferenceFromMemberExpression: function (node) {
|
|
344
385
|
if (node &&
|
|
345
386
|
node.object &&
|
|
346
|
-
node.object.type ===
|
|
387
|
+
node.object.type === "Identifier" &&
|
|
347
388
|
node.object.name &&
|
|
348
389
|
node.property &&
|
|
349
|
-
node.property.type ===
|
|
390
|
+
node.property.type === "Identifier" &&
|
|
350
391
|
node.property.name &&
|
|
351
|
-
node.parent.type !==
|
|
352
|
-
return [node.object.name, node.property.name].join(
|
|
392
|
+
node.parent.type !== "MemberExpression") {
|
|
393
|
+
return [node.object.name, node.property.name].join(".");
|
|
353
394
|
}
|
|
354
395
|
},
|
|
355
396
|
isEitherShortHand: function (property1, property2) {
|
|
356
|
-
const shorthands = [
|
|
397
|
+
const shorthands = ["margin", "padding", "border", "flex"];
|
|
357
398
|
if (shorthands.includes(property1)) {
|
|
358
399
|
return property2.startsWith(property1);
|
|
359
400
|
}
|