@fluentui/eslint-plugin-react-icons 0.0.1
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 +83 -0
- package/docs/rules/prefer-resizable.md +80 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.js +37 -0
- package/lib/rules/prefer-resizable/icon-sizes.generated.d.ts +12 -0
- package/lib/rules/prefer-resizable/icon-sizes.generated.js +34 -0
- package/lib/rules/prefer-resizable/index.d.ts +7 -0
- package/lib/rules/prefer-resizable/index.js +126 -0
- package/lib/rules/prefer-resizable/sized-icons.d.ts +21 -0
- package/lib/rules/prefer-resizable/sized-icons.js +38 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @fluentui/eslint-plugin-react-icons
|
|
2
|
+
|
|
3
|
+
ESLint plugin for [`@fluentui/react-icons`](https://github.com/microsoft/fluentui-system-icons/tree/main/packages/react-icons).
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This ESLint plugin enforces best practices and coding standards when consuming
|
|
8
|
+
Fluent UI System and Brand Icons. It helps developers:
|
|
9
|
+
|
|
10
|
+
- Maintain consistency across `@fluentui/react-icons` usage
|
|
11
|
+
- Catch common mistakes and anti-patterns early
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# npm
|
|
17
|
+
npm install --save-dev @fluentui/eslint-plugin-react-icons
|
|
18
|
+
|
|
19
|
+
# yarn
|
|
20
|
+
yarn add --dev @fluentui/eslint-plugin-react-icons
|
|
21
|
+
|
|
22
|
+
# pnpm
|
|
23
|
+
pnpm add --save-dev @fluentui/eslint-plugin-react-icons
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Flat Config (ESLint 9+, recommended)
|
|
29
|
+
|
|
30
|
+
Add the plugin to your `eslint.config.js`:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
const fluentuiReactIcons = require('@fluentui/eslint-plugin-react-icons');
|
|
34
|
+
|
|
35
|
+
module.exports = [
|
|
36
|
+
fluentuiReactIcons.configs.recommended,
|
|
37
|
+
// Your other configs...
|
|
38
|
+
];
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or configure individual rules manually:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
const fluentuiReactIcons = require('@fluentui/eslint-plugin-react-icons');
|
|
45
|
+
|
|
46
|
+
module.exports = [
|
|
47
|
+
{
|
|
48
|
+
plugins: {
|
|
49
|
+
'@fluentui/react-icons': fluentuiReactIcons,
|
|
50
|
+
},
|
|
51
|
+
rules: {
|
|
52
|
+
'@fluentui/react-icons/prefer-resizable': 'warn',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Legacy Config (ESLint 8 and below)
|
|
59
|
+
|
|
60
|
+
Add the plugin to your `.eslintrc.js`:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
module.exports = {
|
|
64
|
+
plugins: ['@fluentui/react-icons'],
|
|
65
|
+
extends: ['plugin:@fluentui/react-icons/recommended-legacy'],
|
|
66
|
+
};
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Available Rules
|
|
70
|
+
|
|
71
|
+
💼 Configurations enabled in.\
|
|
72
|
+
🔧 Whether the rule has an auto-fixer and/or has suggestions.
|
|
73
|
+
|
|
74
|
+
| Name | Description | 💼 | 🔧 |
|
|
75
|
+
| ---------------------------------------------------- | ----------------------------------------------------------------- | --- | --- |
|
|
76
|
+
| [prefer-resizable](./docs/rules/prefer-resizable.md) | Prefer resizable Fluent icons over fixed-size ("sized") variants. | ✅ | 💡 |
|
|
77
|
+
|
|
78
|
+
💼 ✅ = enabled in the `recommended` config (as `warn`).\
|
|
79
|
+
🔧 = fixable , 💡 = has suggestions.
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# prefer-resizable
|
|
2
|
+
|
|
3
|
+
Prefer resizable Fluent icons over fixed-size ("sized") variants.
|
|
4
|
+
|
|
5
|
+
## Rule Details
|
|
6
|
+
|
|
7
|
+
`@fluentui/react-icons` (and `@fluentui/react-brand-icons`) ship each icon in two
|
|
8
|
+
shapes:
|
|
9
|
+
|
|
10
|
+
- **Resizable** — e.g. `SendRegular`, `AccessTimeFilled`. Rendered at `1em`, so
|
|
11
|
+
they scale to any size via `fontSize` / CSS and let a bundler ship a single glyph
|
|
12
|
+
that can be reused at multiple sizes.
|
|
13
|
+
- **Sized** — e.g. `Send24Regular`, `AccessTime24Filled`. Locked to a design pixel
|
|
14
|
+
size; each size is a separate export and its hardcoded geometry ignores
|
|
15
|
+
`fontSize`.
|
|
16
|
+
|
|
17
|
+
This rule flags imports (and namespace usages) of sized icons and offers a
|
|
18
|
+
suggestion to switch to the resizable equivalent.
|
|
19
|
+
|
|
20
|
+
## How sized icons are detected
|
|
21
|
+
|
|
22
|
+
Detection is data-driven, generated from `@fluentui/react-icons` `metadata.json`:
|
|
23
|
+
|
|
24
|
+
- A name is "sized" when it ends with a real two-digit Fluent size
|
|
25
|
+
(`10, 12, 16, 20, 24, 28, 32, 48`) immediately before its variant suffix
|
|
26
|
+
(`Filled` / `Regular` / `Color` / `Light`).
|
|
27
|
+
- Names whose trailing digits are part of a product name rather than a size
|
|
28
|
+
(e.g. `Battery10Regular`, `Fps120Regular`, `Timer10Filled`) are excluded via a
|
|
29
|
+
generated collision denylist, so they are never mis-flagged.
|
|
30
|
+
|
|
31
|
+
The generated data is verified to classify every icon exactly as `metadata.json` does;
|
|
32
|
+
regenerate it with `yarn generate:data` when the icon set changes.
|
|
33
|
+
|
|
34
|
+
## Examples
|
|
35
|
+
|
|
36
|
+
### ❌ Incorrect
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { Send24Regular } from '@fluentui/react-icons';
|
|
40
|
+
import { Send24Filled } from '@fluentui/react-brand-icons';
|
|
41
|
+
import * as Icons from '@fluentui/react-icons';
|
|
42
|
+
|
|
43
|
+
const a = <Send24Regular />;
|
|
44
|
+
const b = <Icons.Send24Regular />;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### ✅ Correct
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { SendRegular } from '@fluentui/react-icons';
|
|
51
|
+
import * as Icons from '@fluentui/react-icons';
|
|
52
|
+
|
|
53
|
+
const a = <SendRegular fontSize={24} />;
|
|
54
|
+
const b = <Icons.SendRegular />;
|
|
55
|
+
|
|
56
|
+
// Product names that merely end in a size-shaped number are fine:
|
|
57
|
+
import { Battery10Regular, Fps120Regular } from '@fluentui/react-icons';
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Options
|
|
61
|
+
|
|
62
|
+
```jsonc
|
|
63
|
+
{
|
|
64
|
+
"@fluentui/react-icons/prefer-resizable": [
|
|
65
|
+
"warn",
|
|
66
|
+
{
|
|
67
|
+
// Module sources treated as Fluent icon packages (matched exactly or as a
|
|
68
|
+
// subpath prefix, e.g. "@fluentui/react-icons/fonts/send").
|
|
69
|
+
"sources": ["@fluentui/react-icons", "@fluentui/react-brand-icons"],
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## When Not To Use It
|
|
76
|
+
|
|
77
|
+
If you intentionally rely on a specific pixel size's optical tuning and do not want
|
|
78
|
+
to migrate those usages, disable the rule (or the specific lines). Note that
|
|
79
|
+
resizable icons are the 20px artwork made scalable, so swapping a sized icon may
|
|
80
|
+
change its optical rendering.
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const meta: {
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const rules: {
|
|
7
|
+
"prefer-resizable": TSESLint.RuleModule<import("./rules/prefer-resizable").MessageIds, import("./rules/prefer-resizable").Options, TSESLint.RuleListener>;
|
|
8
|
+
};
|
|
9
|
+
export declare const configs: Record<string, TSESLint.FlatConfig.Config | TSESLint.ClassicConfig.Config>;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configs = exports.rules = exports.meta = void 0;
|
|
4
|
+
const node_module_1 = require("node:module");
|
|
5
|
+
const prefer_resizable_1 = require("./rules/prefer-resizable");
|
|
6
|
+
// Read name/version from package.json. A runtime `require` (relative to the built
|
|
7
|
+
// `lib/` output) is used instead of a static `import ... from '../package.json'`
|
|
8
|
+
// because the latter is outside tsc's `rootDir` and would break the flat `lib/`
|
|
9
|
+
// output layout.
|
|
10
|
+
const { name, version } = (0, node_module_1.createRequire)(__filename)('../package.json');
|
|
11
|
+
exports.meta = {
|
|
12
|
+
name,
|
|
13
|
+
version,
|
|
14
|
+
};
|
|
15
|
+
exports.rules = {
|
|
16
|
+
[prefer_resizable_1.RULE_NAME]: prefer_resizable_1.rule,
|
|
17
|
+
};
|
|
18
|
+
const plugin = { meta: exports.meta, rules: exports.rules };
|
|
19
|
+
exports.configs = {
|
|
20
|
+
// Flat config (ESLint 9+, recommended).
|
|
21
|
+
recommended: {
|
|
22
|
+
name: '@fluentui/react-icons/recommended',
|
|
23
|
+
plugins: {
|
|
24
|
+
'@fluentui/react-icons': plugin,
|
|
25
|
+
},
|
|
26
|
+
rules: {
|
|
27
|
+
[`@fluentui/react-icons/${prefer_resizable_1.RULE_NAME}`]: 'warn',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
// Legacy config (ESLint 8 and below).
|
|
31
|
+
'recommended-legacy': {
|
|
32
|
+
plugins: ['@fluentui/react-icons'],
|
|
33
|
+
rules: {
|
|
34
|
+
[`@fluentui/react-icons/${prefer_resizable_1.RULE_NAME}`]: 'warn',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pixel sizes used by "sized" Fluent icon variants (e.g. `AccessTime24Filled`).
|
|
3
|
+
* All Fluent icon sizes are two digits; a sized export name embeds one of these
|
|
4
|
+
* immediately before its variant suffix.
|
|
5
|
+
*/
|
|
6
|
+
export declare const SIZED_ICON_SIZES: readonly number[];
|
|
7
|
+
/**
|
|
8
|
+
* "resizable" icon names that syntactically look "sized" because a product name
|
|
9
|
+
* ends in a two-digit number matching a real size (e.g. `Battery10Regular`, where
|
|
10
|
+
* "10" is a battery level, not a size). These must NOT be flagged as sized.
|
|
11
|
+
*/
|
|
12
|
+
export declare const RESIZABLE_COLLISIONS: readonly string[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// AUTO-GENERATED by scripts/generate-icon-sizes.mjs — do not edit by hand.
|
|
3
|
+
// Regenerate with: npm run generate:data (inside packages/eslint-plugin-react-icons).
|
|
4
|
+
//
|
|
5
|
+
// Source of truth: @fluentui/react-icons metadata.json (icon "type" field).
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.RESIZABLE_COLLISIONS = exports.SIZED_ICON_SIZES = void 0;
|
|
8
|
+
/**
|
|
9
|
+
* Pixel sizes used by "sized" Fluent icon variants (e.g. `AccessTime24Filled`).
|
|
10
|
+
* All Fluent icon sizes are two digits; a sized export name embeds one of these
|
|
11
|
+
* immediately before its variant suffix.
|
|
12
|
+
*/
|
|
13
|
+
exports.SIZED_ICON_SIZES = [10, 12, 16, 20, 24, 28, 32, 48];
|
|
14
|
+
/**
|
|
15
|
+
* "resizable" icon names that syntactically look "sized" because a product name
|
|
16
|
+
* ends in a two-digit number matching a real size (e.g. `Battery10Regular`, where
|
|
17
|
+
* "10" is a battery level, not a size). These must NOT be flagged as sized.
|
|
18
|
+
*/
|
|
19
|
+
exports.RESIZABLE_COLLISIONS = [
|
|
20
|
+
'Battery10Filled',
|
|
21
|
+
'Battery10Regular',
|
|
22
|
+
'BatteryCharge10Filled',
|
|
23
|
+
'BatteryCharge10Regular',
|
|
24
|
+
'Fps120Filled',
|
|
25
|
+
'Fps120Regular',
|
|
26
|
+
'Fps124Filled',
|
|
27
|
+
'Fps124Regular',
|
|
28
|
+
'SkipBack10Filled',
|
|
29
|
+
'SkipBack10Regular',
|
|
30
|
+
'SkipForward10Filled',
|
|
31
|
+
'SkipForward10Regular',
|
|
32
|
+
'Timer10Filled',
|
|
33
|
+
'Timer10Regular',
|
|
34
|
+
];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const RULE_NAME = "prefer-resizable";
|
|
3
|
+
export type MessageIds = 'preferResizable' | 'useResizable';
|
|
4
|
+
export type Options = [{
|
|
5
|
+
sources?: string[];
|
|
6
|
+
}];
|
|
7
|
+
export declare const rule: ESLintUtils.RuleModule<MessageIds, Options, ESLintUtils.RuleListener>;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) Microsoft Corporation.
|
|
3
|
+
// Licensed under the MIT license.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.rule = exports.RULE_NAME = void 0;
|
|
6
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
7
|
+
const sized_icons_1 = require("./sized-icons");
|
|
8
|
+
exports.RULE_NAME = 'prefer-resizable';
|
|
9
|
+
const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/microsoft/fluentui-system-icons/blob/main/packages/eslint-plugin-react-icons/docs/rules/${name}.md`);
|
|
10
|
+
const DEFAULT_SOURCES = ['@fluentui/react-icons', '@fluentui/react-brand-icons'];
|
|
11
|
+
exports.rule = createRule({
|
|
12
|
+
name: exports.RULE_NAME,
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'suggestion',
|
|
15
|
+
docs: {
|
|
16
|
+
description: 'Prefer resizable Fluent icons over fixed-size ("sized") variants. Resizable icons scale to any size and reduce bundle size.',
|
|
17
|
+
},
|
|
18
|
+
hasSuggestions: true,
|
|
19
|
+
messages: {
|
|
20
|
+
preferResizable: 'Prefer the resizable "{{resizable}}" icon over the fixed-size "{{sized}}" icon. Resizable icons scale to any size (via the `fontSize`/CSS) and reduce bundle size.',
|
|
21
|
+
useResizable: 'Replace "{{sized}}" with "{{resizable}}".',
|
|
22
|
+
},
|
|
23
|
+
schema: [
|
|
24
|
+
{
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
sources: {
|
|
28
|
+
type: 'array',
|
|
29
|
+
items: { type: 'string' },
|
|
30
|
+
description: 'Module sources treated as Fluent icon packages.',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
additionalProperties: false,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
defaultOptions: [{ sources: DEFAULT_SOURCES }],
|
|
38
|
+
create(context, [options]) {
|
|
39
|
+
const sources = options.sources ?? DEFAULT_SOURCES;
|
|
40
|
+
const { sourceCode } = context;
|
|
41
|
+
/** Local identifiers bound to `import * as X` from an icon source. */
|
|
42
|
+
const namespaceLocals = new Set();
|
|
43
|
+
function isIconSource(value) {
|
|
44
|
+
return sources.some((source) => value === source || value.startsWith(`${source}/`));
|
|
45
|
+
}
|
|
46
|
+
function reportNamed(specifier) {
|
|
47
|
+
const importedName = specifier.imported.name;
|
|
48
|
+
const resizable = (0, sized_icons_1.getResizableIconName)(importedName);
|
|
49
|
+
if (resizable === null) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const isAliased = specifier.local.range[0] !== specifier.imported.range[0];
|
|
53
|
+
const fix = (fixer) => {
|
|
54
|
+
if (isAliased) {
|
|
55
|
+
// `import { Send24Regular as Foo }` -> only rename the imported name.
|
|
56
|
+
return [fixer.replaceText(specifier.imported, resizable)];
|
|
57
|
+
}
|
|
58
|
+
// `import { Send24Regular }` -> rename the binding and every usage.
|
|
59
|
+
const [variable] = sourceCode.getDeclaredVariables(specifier);
|
|
60
|
+
const fixes = [fixer.replaceText(specifier.local, resizable)];
|
|
61
|
+
for (const reference of variable?.references ?? []) {
|
|
62
|
+
fixes.push(fixer.replaceText(reference.identifier, resizable));
|
|
63
|
+
}
|
|
64
|
+
return fixes;
|
|
65
|
+
};
|
|
66
|
+
context.report({
|
|
67
|
+
node: specifier.imported,
|
|
68
|
+
messageId: 'preferResizable',
|
|
69
|
+
data: { sized: importedName, resizable },
|
|
70
|
+
suggest: [
|
|
71
|
+
{
|
|
72
|
+
messageId: 'useResizable',
|
|
73
|
+
data: { sized: importedName, resizable },
|
|
74
|
+
fix,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function reportNamespaceMember(property) {
|
|
80
|
+
const resizable = (0, sized_icons_1.getResizableIconName)(property.name);
|
|
81
|
+
if (resizable === null) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const fix = (fixer) => fixer.replaceText(property, resizable);
|
|
85
|
+
context.report({
|
|
86
|
+
node: property,
|
|
87
|
+
messageId: 'preferResizable',
|
|
88
|
+
data: { sized: property.name, resizable },
|
|
89
|
+
suggest: [
|
|
90
|
+
{
|
|
91
|
+
messageId: 'useResizable',
|
|
92
|
+
data: { sized: property.name, resizable },
|
|
93
|
+
fix,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
ImportDeclaration(node) {
|
|
100
|
+
if (!isIconSource(node.source.value)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
for (const specifier of node.specifiers) {
|
|
104
|
+
if (specifier.type === 'ImportSpecifier') {
|
|
105
|
+
reportNamed(specifier);
|
|
106
|
+
}
|
|
107
|
+
else if (specifier.type === 'ImportNamespaceSpecifier') {
|
|
108
|
+
namespaceLocals.add(specifier.local.name);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
'JSXMemberExpression[object.type="JSXIdentifier"]'(node) {
|
|
113
|
+
const object = node.object;
|
|
114
|
+
if (namespaceLocals.has(object.name)) {
|
|
115
|
+
reportNamespaceMember(node.property);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
'MemberExpression[object.type="Identifier"][property.type="Identifier"][computed=false]'(node) {
|
|
119
|
+
const object = node.object;
|
|
120
|
+
if (namespaceLocals.has(object.name)) {
|
|
121
|
+
reportNamespaceMember(node.property);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Matches a "sized" Fluent icon export name: a two-digit pixel size immediately
|
|
3
|
+
* before the variant suffix, e.g. `AccessTime24Filled`. Sizes are sorted
|
|
4
|
+
* descending so any multi-digit size (should one ever be added) is matched first.
|
|
5
|
+
*/
|
|
6
|
+
export declare const SIZED_ICON_RE: RegExp;
|
|
7
|
+
/**
|
|
8
|
+
* Whether an icon export name refers to a fixed-size ("sized") icon variant
|
|
9
|
+
* (e.g. `Send24Regular`) rather than a resizable one (e.g. `SendRegular`).
|
|
10
|
+
*
|
|
11
|
+
* Names that look sized but are actually resizable (a product name ending in a
|
|
12
|
+
* size-shaped number, e.g. `Battery10Regular`) are excluded via the generated
|
|
13
|
+
* collision denylist.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isSizedIconName(name: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The resizable equivalent of a sized icon name (the size token stripped), or
|
|
18
|
+
* `null` when the name is not a sized icon. E.g. `Send24Regular` -> `SendRegular`,
|
|
19
|
+
* `PresenceDnd10Filled` -> `PresenceDndFilled`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getResizableIconName(name: string): string | null;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) Microsoft Corporation.
|
|
3
|
+
// Licensed under the MIT license.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.getResizableIconName = exports.isSizedIconName = exports.SIZED_ICON_RE = void 0;
|
|
6
|
+
const icon_sizes_generated_1 = require("./icon-sizes.generated");
|
|
7
|
+
const VARIANT_SUFFIX = 'Filled|Regular|Color|Light';
|
|
8
|
+
/**
|
|
9
|
+
* Matches a "sized" Fluent icon export name: a two-digit pixel size immediately
|
|
10
|
+
* before the variant suffix, e.g. `AccessTime24Filled`. Sizes are sorted
|
|
11
|
+
* descending so any multi-digit size (should one ever be added) is matched first.
|
|
12
|
+
*/
|
|
13
|
+
exports.SIZED_ICON_RE = new RegExp(`(${[...icon_sizes_generated_1.SIZED_ICON_SIZES].sort((a, b) => b - a).join('|')})(${VARIANT_SUFFIX})$`);
|
|
14
|
+
const collisions = new Set(icon_sizes_generated_1.RESIZABLE_COLLISIONS);
|
|
15
|
+
/**
|
|
16
|
+
* Whether an icon export name refers to a fixed-size ("sized") icon variant
|
|
17
|
+
* (e.g. `Send24Regular`) rather than a resizable one (e.g. `SendRegular`).
|
|
18
|
+
*
|
|
19
|
+
* Names that look sized but are actually resizable (a product name ending in a
|
|
20
|
+
* size-shaped number, e.g. `Battery10Regular`) are excluded via the generated
|
|
21
|
+
* collision denylist.
|
|
22
|
+
*/
|
|
23
|
+
function isSizedIconName(name) {
|
|
24
|
+
return exports.SIZED_ICON_RE.test(name) && !collisions.has(name);
|
|
25
|
+
}
|
|
26
|
+
exports.isSizedIconName = isSizedIconName;
|
|
27
|
+
/**
|
|
28
|
+
* The resizable equivalent of a sized icon name (the size token stripped), or
|
|
29
|
+
* `null` when the name is not a sized icon. E.g. `Send24Regular` -> `SendRegular`,
|
|
30
|
+
* `PresenceDnd10Filled` -> `PresenceDndFilled`.
|
|
31
|
+
*/
|
|
32
|
+
function getResizableIconName(name) {
|
|
33
|
+
if (!isSizedIconName(name)) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return name.replace(exports.SIZED_ICON_RE, '$2');
|
|
37
|
+
}
|
|
38
|
+
exports.getResizableIconName = getResizableIconName;
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fluentui/eslint-plugin-react-icons",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "ESLint plugin for @fluentui/react-icons and @fluentui/react-brand-icons",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"typings": "lib/index.d.ts",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"require": "./lib/index.js",
|
|
12
|
+
"default": "./lib/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"clean": "git clean -fXd lib/",
|
|
18
|
+
"generate:data": "node scripts/generate-icon-sizes.mjs --metadata ../react-icons/metadata.json --out src/rules/prefer-resizable/icon-sizes.generated.ts",
|
|
19
|
+
"build": "yarn clean && yarn generate:data && yarn run -T tsc -p tsconfig.lib.json",
|
|
20
|
+
"lint": "yarn run -T eslint src package.json",
|
|
21
|
+
"test": "yarn run -T vitest run",
|
|
22
|
+
"type-check": "yarn run -T tsc -p tsconfig.json",
|
|
23
|
+
"type-check:infra": "yarn run -T tsc -p tsconfig.utils.json"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20.0.0"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/microsoft/fluentui-system-icons.git",
|
|
31
|
+
"directory": "packages/eslint-plugin-react-icons"
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/microsoft/fluentui-system-icons/issues"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@typescript-eslint/utils": "7.18.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"eslint": ">=8.0.0"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"lib/*",
|
|
45
|
+
"docs"
|
|
46
|
+
]
|
|
47
|
+
}
|