Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eslint-rules): implement no-missing-jsx-pragma in order to automate and unify slot API usage #32842

Merged
merged 7 commits into from
Oct 10, 2024
7 changes: 6 additions & 1 deletion tools/eslint-rules/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RULE_NAME as noRestrictedGlobalsName, rule as noRestrictedGlobals } from './rules/no-restricted-globals';
import { RULE_NAME as noMissingJsxPragmaName, rule as noMissingJsxPragma } from './rules/no-missing-jsx-pragma';
import {
RULE_NAME as consistentCallbackTypeName,
rule as consistentCallbackType,
Expand Down Expand Up @@ -28,5 +29,9 @@ module.exports = {
* [myCustomRuleName]: myCustomRule
* }
*/
rules: { [consistentCallbackTypeName]: consistentCallbackType, [noRestrictedGlobalsName]: noRestrictedGlobals },
rules: {
[consistentCallbackTypeName]: consistentCallbackType,
[noRestrictedGlobalsName]: noRestrictedGlobals,
[noMissingJsxPragmaName]: noMissingJsxPragma,
},
};
6 changes: 5 additions & 1 deletion tools/eslint-rules/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@
"sourceRoot": "tools/eslint-rules",
"projectType": "library",
"tags": ["platform:node", "tools"],
"targets": {}
"targets": {
"type-check": {
"executor": "@fluentui/workspace-plugin:type-check"
}
}
}
359 changes: 359 additions & 0 deletions tools/eslint-rules/rules/no-missing-jsx-pragma.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,359 @@
import { RuleTester } from '@typescript-eslint/rule-tester';
import { rule, RULE_NAME } from './no-missing-jsx-pragma';

const ruleTester = new RuleTester({
parser: '@typescript-eslint/parser',
parserOptions: { ecmaFeatures: { jsx: true } },
});

ruleTester.run(RULE_NAME, rule, {
valid: [
// no slot api used case
{
Hotell marked this conversation as resolved.
Show resolved Hide resolved
options: [{ runtime: 'automatic' }],
code: `
export const renderFoo = () => {
return (
<div>
hello
</div>
);
}
`,
},

// no slot api used case
{
options: [{ runtime: 'classic' }],
code: `
export const renderFoo = () => {
return (
<div>
hello
</div>
);
}
`,
},

// slot api used + valid pragma exist case
{
options: [{ runtime: 'automatic' }],
code: `
/** @jsxImportSource @fluentui/react-jsx-runtime */

import { assertSlots } from '@fluentui/react-utilities';

import type { FooState, FooContextValues, FooSlots } from './Foo.types';

export const renderFoo_unstable = (state: FooState, contextValues: FooContextValues) => {
assertSlots<FooSlots>(state);

return (
<state.root>
<state.button>
hello
</state.button>
</state.root>
);
};
`,
},

// slot api used + valid pragma exist case
{
options: [{ runtime: 'classic' }],
code: `
/** @jsx createElement */
import { createElement } from '@fluentui/react-jsx-runtime';
import { assertSlots } from '@fluentui/react-utilities';

import type { FooState, FooContextValues, FooSlots } from './Foo.types';

export const renderFoo_unstable = (state: FooState, contextValues: FooContextValues) => {
assertSlots<FooSlots>(state);

return (
<state.root>
<state.button>
hello
</state.button>
</state.root>
);
};
`,
},

// slot api (.always) used in a non conformant way + valid pragma exist case
{
options: [{ runtime: 'automatic' }],
code: `
/** @jsxImportSource @fluentui/react-jsx-runtime */
import { slot, getIntrinsicElementProps } from '@fluentui/react-utilities';

export const Test = (props: {}) => {
const SlotComponent = slot.always(getIntrinsicElementProps('span', {}),{ elementType: 'span' })

return (
<div>
<SlotComponent/>
</div>
);
};
`,
},

// slot api (.optional) used in a non conformant way + valid pragma exist case
{
options: [{ runtime: 'automatic' }],
code: `
/** @jsxImportSource @fluentui/react-jsx-runtime */
import { slot, getIntrinsicElementProps } from '@fluentui/react-utilities';

export const Test = (props: {}) => {
const SlotComponent = slot.optional(getIntrinsicElementProps('span', {}),{ elementType: 'span' })

return (
<div>
<SlotComponent/>
</div>
);
};
`,
},

// slot api (both .optional,.always) used in a non conformant way + valid pragma exist case
{
options: [{ runtime: 'automatic' }],
code: `
/** @jsxImportSource @fluentui/react-jsx-runtime */
import { slot, getIntrinsicElementProps } from '@fluentui/react-utilities';

export const Test = (props: {}) => {
const SlotComponent = slot.always(getIntrinsicElementProps('span', {}),{ elementType: 'span' })
const SlotOptionalComponent = slot.optional(getIntrinsicElementProps('span', {}),{ elementType: 'span' })

return (
<div>
<SlotComponent/>
<SlotOptionalComponent/>
</div>
);
};
`,
},

// slot api (.always) used in a non conformant way + no direct JSX rendering + no pragma required case
{
options: [{ runtime: 'automatic' }],
code: `
import { slot, getIntrinsicElementProps } from '@fluentui/react-utilities';

export const factory = (props: {}) => {
const SlotComponent = slot.always(getIntrinsicElementProps('span', {}),{ elementType: 'span' })
const InlineCmp = () => <div>inline</div>

return { SlotComponent, InlineCmp };
};
`,
},
],
invalid: [
// slot api used + missing pragma
{
options: [{ runtime: 'automatic' }],
code: `
import { assertSlots } from '@fluentui/react-utilities';
import type { FooState, FooContextValues, FooSlots } from './Foo.types';
export const renderFoo_unstable = (state: FooState, contextValues: FooContextValues) => {
assertSlots<FooSlots>(state);
return (
<state.root>
<state.button>
hello
</state.button>
</state.root>
);
};
`,
errors: [{ messageId: 'missingJsxImportSource' }],
},

// slot api used + missing pragma
{
options: [{ runtime: 'classic' }],
code: `
import { assertSlots } from '@fluentui/react-utilities';
import type { FooState, FooContextValues, FooSlots } from './Foo.types';
export const renderFoo_unstable = (state: FooState, contextValues: FooContextValues) => {
assertSlots<FooSlots>(state);
return (
<state.root>
<state.button>
hello
</state.button>
</state.root>
);
};
`,
errors: [{ messageId: 'missingJsxPragma' }],
},

// slot api used + pragma present + factory import missing
{
options: [{ runtime: 'classic' }],
code: `
/** @jsx createElement */
import { assertSlots } from '@fluentui/react-utilities';
import type { FooState, FooContextValues, FooSlots } from './Foo.types';
export const renderFoo_unstable = (state: FooState, contextValues: FooContextValues) => {
assertSlots<FooSlots>(state);
return (
<state.root>
<state.button>
hello
</state.button>
</state.root>
);
};
`,
errors: [{ messageId: 'missingCreateElementFactoryImport' }],
},

// slot api used + pragma present + invalid pragma for automatic mode
{
options: [{ runtime: 'automatic' }],
code: `
/** @jsx createElement */
import { assertSlots } from '@fluentui/react-utilities';
import { createElement } from '@fluentui/react-jsx-runtime';
import type { FooState, FooContextValues, FooSlots } from './Foo.types';
export const renderFoo_unstable = (state: FooState, contextValues: FooContextValues) => {
assertSlots<FooSlots>(state);
return (
<state.root>
<state.button>
hello
</state.button>
</state.root>
);
};
`,
errors: [{ messageId: 'invalidJSXPragmaForAutomatic' }],
},

// slot api used + pragma present + invalid pragma for classic mode
{
options: [{ runtime: 'classic' }],
code: `
/** @jsxImportSource @fluentui/react-jsx-runtime */
import { assertSlots } from '@fluentui/react-utilities';
import type { FooState, FooContextValues, FooSlots } from './Foo.types';
export const renderFoo_unstable = (state: FooState, contextValues: FooContextValues) => {
assertSlots<FooSlots>(state);
return (
<state.root>
<state.button>
hello
</state.button>
</state.root>
);
};
`,
errors: [{ messageId: 'invalidJSXPragmaForClassic' }],
},

// no slot api used for JSX rendering + pragma present + pragma should not be specified
{
options: [{ runtime: 'automatic' }],
code: `
/** @jsxImportSource @fluentui/react-jsx-runtime */
import { assertSlots } from '@fluentui/react-utilities';
import type { FooState, FooContextValues, FooSlots } from './Foo.types';
export const renderFoo_unstable = (state: FooState, contextValues: FooContextValues) => {
assertSlots<FooSlots>(state);
return (
<div>hello</div>
);
};
`,
errors: [{ messageId: 'redundantPragma' }],
},

// no slot api used for JSX rendering + pragma present + pragma should not be specified
{
options: [{ runtime: 'classic' }],
code: `
/** @jsxImportSource @fluentui/react-jsx-runtime */
import { assertSlots } from '@fluentui/react-utilities';
import type { FooState, FooContextValues, FooSlots } from './Foo.types';
export const renderFoo_unstable = (state: FooState, contextValues: FooContextValues) => {
assertSlots<FooSlots>(state);
return (
<div>hello</div>
);
};
`,
errors: [{ messageId: 'redundantPragma' }],
},

// slot api (.always) used in a non conformant way + direct JSX rendering + pragma is missing
{
options: [{ runtime: 'automatic' }],
code: `
import { slot, getIntrinsicElementProps } from '@fluentui/react-utilities';

export const Test = (props: {}) => {
const SlotComponent = slot.always(getIntrinsicElementProps('span', {}),{ elementType: 'span' })

return (
<div>
<SlotComponent/>
</div>
);
};
`,
errors: [{ messageId: 'missingJsxImportSource' }],
},

// slot api (.optional) used in a non conformant way + direct JSX rendering + pragma is missing
{
options: [{ runtime: 'automatic' }],
code: `
import { slot, getIntrinsicElementProps } from '@fluentui/react-utilities';

export const Test = (props: {}) => {
const SlotComponent = slot.optional(getIntrinsicElementProps('span', {}),{ elementType: 'span' })

return (
<div>
<SlotComponent/>
</div>
);
};
`,
errors: [{ messageId: 'missingJsxImportSource' }],
},

// slot api (.optional,.always) used in a non conformant way + direct JSX rendering + pragma is missing
{
options: [{ runtime: 'automatic' }],
code: `
import { slot, getIntrinsicElementProps } from '@fluentui/react-utilities';

export const Test = (props: {}) => {
const SlotComponent = slot.always(getIntrinsicElementProps('span', {}),{ elementType: 'span' })
const SlotOptionalComponent = slot.optional(getIntrinsicElementProps('span', {}),{ elementType: 'span' })

return (
<div>
<SlotComponent/>
<SlotOptionalComponent/>
</div>
);
};
`,
errors: [{ messageId: 'missingJsxImportSource' }],
},
],
});
Loading
Loading