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 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 '@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';
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
- '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,
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: 'oxlint-plugin-react-native' },
17
+ meta: { name: "oxlint-plugin-react-native" },
18
18
  rules: allRules,
19
19
  });
@@ -1,6 +1,6 @@
1
- import { detect } from '../util/Components.js';
2
- import { StyleSheets, astHelpers } from '../util/stylesheet.js';
3
- import * as util from 'util';
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
- 'Program:exit': () => {
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: 'Color literal: {{expression}}',
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 '../util/Components.js';
2
- import { StyleSheets, astHelpers } from '../util/stylesheet.js';
3
- import * as util from 'util';
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
- 'Program:exit': () => {
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: 'Inline style: {{expression}}',
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 === 'JSXElement' &&
4
- node.openingElement.type === 'JSXOpeningElement') {
3
+ if (node.type === "JSXElement" &&
4
+ node.openingElement.type === "JSXOpeningElement") {
5
5
  let object = node.openingElement.name;
6
- while (object.type === 'JSXMemberExpression') {
7
- if (object.property.type === 'JSXIdentifier') {
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 === 'JSXIdentifier') {
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 === 'JSXElement') {
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
- 'Text',
40
- 'TSpan',
41
- 'StyledText',
42
- 'Animated.Text',
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 === 'TemplateLiteral'
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})` : 'Whitespace(s)';
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 = ['JSXExpressionContainer', 'JSXElement'];
63
- if (typeof node.value !== 'string' ||
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 === 'JSXAttribute'))
66
+ (node.parent.parent && node.parent.parent.type === "JSXAttribute"))
67
67
  return;
68
- const isStringLiteral = parentType === 'JSXExpressionContainer';
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 !== 'string' || hasOnlyLineBreak(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 !== 'JSXExpressionContainer' ||
82
- (node.parent.parent && node.parent.parent.type === 'JSXAttribute'))
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: 'object',
94
+ type: "object",
95
95
  properties: {
96
96
  skip: {
97
- type: 'array',
97
+ type: "array",
98
98
  items: {
99
- type: 'string',
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: 'Single element style arrays are not necessary and cause unnecessary re-renders',
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 !== 'style')
18
+ if (node.name.name !== "style")
19
19
  return;
20
20
  if (!node.value.expression)
21
21
  return;
22
- if (node.value.expression.type !== 'ArrayExpression')
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: 'Disallow single element style arrays. These cause unnecessary re-renders as the identity of the array always changes',
34
- category: 'Stylistic Issues',
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: 'code',
38
+ fixable: "code",
39
39
  },
40
40
  createOnce: rule,
41
41
  };
@@ -1,5 +1,5 @@
1
- import { detect } from '../util/Components.js';
2
- import { StyleSheets, astHelpers } from '../util/stylesheet.js';
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
- 'Unused style detected: ',
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
- 'Program:exit': function () {
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 '../util/stylesheet.js';
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 === 'asc' ? 1 : -1);
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 !== 'Property' || current.type !== 'Property') {
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 === 'style properties' &&
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] || 'asc';
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 === 'asc'
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, 'class names', node, order, options, sourceCode, isValidOrder);
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, 'style properties', node, order, options, sourceCode, isValidOrder);
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: 'code',
102
+ fixable: "code",
103
103
  schema: [
104
104
  {
105
- enum: ['asc', 'desc'],
105
+ enum: ["asc", "desc"],
106
106
  },
107
107
  {
108
- type: 'object',
108
+ type: "object",
109
109
  properties: {
110
110
  ignoreClassNames: {
111
- type: 'boolean',
111
+ type: "boolean",
112
112
  },
113
113
  ignoreStyleProperties: {
114
- type: 'boolean',
114
+ type: "boolean",
115
115
  },
116
116
  },
117
117
  additionalProperties: false,
@@ -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 'ReturnStatement':
80
- property = 'argument';
79
+ case "ReturnStatement":
80
+ property = "argument";
81
81
  break;
82
- case 'ArrowFunctionExpression':
83
- property = 'body';
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 === 'JSXElement' ||
90
- node[property].type === 'JSXFragment');
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 === 'createElement';
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 !== 'class') {
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 !== 'MethodDefinition';
130
- const isNotArgument = !node.parent || node.parent.type !== 'CallExpression';
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 === 'Identifier') {
147
+ currentNode.property.type === "Identifier") {
148
148
  componentPath.push(currentNode.property.name);
149
149
  }
150
- if (currentNode.object && currentNode.object.type === 'Identifier') {
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 === 'ClassName' ||
175
- defs[i].type === 'FunctionName' ||
176
- defs[i].type === 'Variable') {
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
  }
@@ -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
- return this.styleSheets;
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['react-native/style-sheet-object-names'] || ['StyleSheet'];
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 === 'CallExpression' &&
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 === 'create');
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 === 'CallExpression' &&
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 === 'Property');
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 === 'CallExpression' &&
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 === 'Property') {
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 === 'Property') {
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 'Identifier':
150
+ case "Identifier":
120
151
  return node.name;
121
- case 'Literal':
152
+ case "Literal":
122
153
  return node.value;
123
- case 'TemplateLiteral':
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 === 'JSXAttribute' &&
170
+ return Boolean(node.type === "JSXAttribute" &&
140
171
  node.name &&
141
172
  node.name.name &&
142
- node.name.name.toLowerCase().includes('style'));
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 === 'ObjectExpression') {
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 'MemberExpression':
222
+ case "MemberExpression":
192
223
  styleReference = astHelpers.getStyleReferenceFromExpression(node);
193
224
  return [styleReference];
194
- case 'LogicalExpression':
225
+ case "LogicalExpression":
195
226
  leftStyleReferences = astHelpers.getStyleReferenceFromNode(node.left);
196
227
  rightStyleReferences = astHelpers.getStyleReferenceFromNode(node.right);
197
- return [].concat(leftStyleReferences).concat(rightStyleReferences);
198
- case 'ConditionalExpression':
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 [].concat(leftStyleReferences).concat(rightStyleReferences);
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 === 'ObjectExpression') {
247
+ if (node.type === "ObjectExpression") {
213
248
  return [astHelpers.getStyleObjectFromExpression(node)];
214
249
  }
215
250
  switch (node.type) {
216
- case 'LogicalExpression':
251
+ case "LogicalExpression":
217
252
  leftStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.left);
218
- rightStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.right);
253
+ rightStyleObjectExpression =
254
+ astHelpers.getStyleObjectExpressionFromNode(node.right);
219
255
  return []
220
256
  .concat(leftStyleObjectExpression)
221
257
  .concat(rightStyleObjectExpression);
222
- case 'ConditionalExpression':
258
+ case "ConditionalExpression":
223
259
  leftStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.consequent);
224
- rightStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.alternate);
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 === 'ObjectExpression') {
275
+ if (node.type === "ObjectExpression") {
239
276
  return [astHelpers.getColorLiteralsFromExpression(node)];
240
277
  }
241
278
  switch (node.type) {
242
- case 'LogicalExpression':
279
+ case "LogicalExpression":
243
280
  leftColorLiterals = astHelpers.getColorLiteralsFromNode(node.left);
244
281
  rightColorLiterals = astHelpers.getColorLiteralsFromNode(node.right);
245
- return [].concat(leftColorLiterals).concat(rightColorLiterals);
246
- case 'ConditionalExpression':
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 [].concat(leftColorLiterals).concat(rightColorLiterals);
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 === 'JSXExpressionContainer' &&
297
+ Boolean(node.type === "JSXExpressionContainer" &&
257
298
  node.expression &&
258
- node.expression.type === 'ArrayExpression'));
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 === 'Literal') {
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 === 'ConditionalExpression') {
325
+ else if (p.value.type === "ConditionalExpression") {
285
326
  const innerNode = p.value;
286
- if (innerNode.consequent.type === 'Literal' ||
287
- innerNode.alternate.type === 'Literal') {
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 === 'UnaryExpression' &&
293
- p.value.operator === '-' &&
294
- p.value.argument.type === 'Literal') {
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 === 'UnaryExpression' &&
299
- p.value.operator === '+' &&
300
- p.value.argument.type === 'Literal') {
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('color') !== -1) {
316
- if (p.value.type === 'Literal') {
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 === 'ConditionalExpression') {
361
+ else if (p.value.type === "ConditionalExpression") {
321
362
  const innerNode = p.value;
322
- if (innerNode.consequent.type === 'Literal' ||
323
- innerNode.alternate.type === 'Literal') {
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 === 'Identifier' &&
387
+ node.object.type === "Identifier" &&
347
388
  node.object.name &&
348
389
  node.property &&
349
- node.property.type === 'Identifier' &&
390
+ node.property.type === "Identifier" &&
350
391
  node.property.name &&
351
- node.parent.type !== 'MemberExpression') {
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 = ['margin', 'padding', 'border', 'flex'];
397
+ const shorthands = ["margin", "padding", "border", "flex"];
357
398
  if (shorthands.includes(property1)) {
358
399
  return property2.startsWith(property1);
359
400
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-native",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "React Native specific linting rules for Oxlint",
5
5
  "keywords": [
6
6
  "oxlint",