Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions config/eslint/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ const config = defineConfig([
'rulesdir/require-a11y-disable-justification': 'error',
'rulesdir/no-direct-pre-insert-fullscreen-under-rhp': 'error',
'rulesdir/no-useOnyx-dependencies-arg': 'error',
'rulesdir/no-raw-typography': 'error',
'rulesdir/prefer-narrow-hook-dependencies': [
'error',
{
Expand Down Expand Up @@ -655,6 +656,14 @@ const config = defineConfig([
rules: {'report-name-utils/no-function-call-in-get-report-name': 'error'},
},

// The typography token files are where raw font sizes and line heights are defined.
{
files: ['src/styles/typography.ts', 'src/styles/variables.ts'],
rules: {
'rulesdir/no-raw-typography': 'off',
},
},

// Restrict `computeReportName` imports everywhere except the one file that
// legitimately consumes it. This block overrides the main `no-restricted-imports`
// for ts/tsx files, so we re-apply the main `restrictedImportPaths`/`restrictedImportPatterns`
Expand Down
21 changes: 21 additions & 0 deletions config/eslint/eslint.seatbelt.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
"../../src/components/CategoryPicker/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/Charts/BarChart/BarChartContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/Charts/hooks/useChartLabelFormats.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/Charts/hooks/useChartLabelMeasurements.ts" "rulesdir/no-raw-typography" 1
"../../src/components/Charts/utils/chartFontAssets.native.ts" "@typescript-eslint/no-unsafe-type-assertion" 13
"../../src/components/Charts/utils/chartFontAssets.ts" "@typescript-eslint/no-unsafe-type-assertion" 13
"../../src/components/Charts/utils/chartFontsCache.ts" "@typescript-eslint/no-unsafe-type-assertion" 2
Expand Down Expand Up @@ -193,6 +194,7 @@
"../../src/components/HTMLEngineProvider/HTMLRenderers/AccountManagerLinkRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/HTMLEngineProvider/HTMLRenderers/ConciergeLinkRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.tsx" "@typescript-eslint/no-deprecated/fontSize" 3
"../../src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2
"../../src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
Expand All @@ -202,6 +204,7 @@
"../../src/components/HTMLEngineProvider/HTMLRenderers/TransactionHistoryLinkRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victorySeriesParser.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getSkiaLineMetrics.ts" "rulesdir/no-raw-typography" 1
"../../src/components/HTMLEngineProvider/index.tsx" "no-restricted-syntax" 1
"../../src/components/HeaderWithBackButton/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2
"../../src/components/Hoverable/ActiveHoverable.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
Expand Down Expand Up @@ -267,6 +270,7 @@
"../../src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx" "react-hooks/refs" 3
"../../src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx" "react-hooks/set-state-in-effect" 3
"../../src/components/MoneyRequestReportView/MoneyRequestReportGroupHeader.tsx" "rulesdir/no-raw-typography" 1
"../../src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2
"../../src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx" "no-restricted-imports" 1
"../../src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2
Expand Down Expand Up @@ -335,12 +339,15 @@
"../../src/components/ReportActionItem/MoneyRequestView.tsx" "@typescript-eslint/no-deprecated/deprecatedGetReportName" 2
"../../src/components/ReportActionItem/TaskPreview.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/ReportActionItem/TaskView.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx" "@typescript-eslint/no-deprecated/fontSize" 3
"../../src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx" "no-restricted-imports" 1
"../../src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx" "rulesdir/no-raw-typography" 1
"../../src/components/ReportActionItem/receiptHoverUtils/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 2
"../../src/components/ReportWelcomeText.tsx" "@typescript-eslint/no-deprecated/deprecatedGetReportName" 1
"../../src/components/RoomHeaderAvatars.tsx" "no-restricted-syntax" 2
"../../src/components/Rule/RuleBooleanBase.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/Rule/TextBase.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2
"../../src/components/SAMLLoadingIndicator.tsx" "@typescript-eslint/no-deprecated/fontSize" 1
"../../src/components/ScreenShareRequestModal.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 1
"../../src/components/ScreenWrapper/ScreenWrapperContainer.tsx" "react-hooks/refs" 2
"../../src/components/Search/FilterComponents/AdvancedFilters/AmountFilterContent.tsx" "no-restricted-imports" 1
Expand Down Expand Up @@ -417,6 +424,7 @@
"../../src/components/SelectionList/BaseSelectionList.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/SelectionList/ListItem/BaseListItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/SelectionList/ListItem/SpendRuleListItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/SelectionList/ListItem/SplitListItem.tsx" "@typescript-eslint/no-deprecated/fontSize" 1
"../../src/components/SelectionList/ListItem/SplitListItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2
"../../src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/SelectionList/components/Footer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
Expand Down Expand Up @@ -457,6 +465,7 @@
"../../src/components/Tables/WorkspaceCompanyCardsTable/WorkspaceCompanyCardsTableRow.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/TagPicker/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/TaxPicker.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/Text.tsx" "@typescript-eslint/no-deprecated/fontSize" 1
"../../src/components/TextInput/BaseTextInput/implementation/index.native.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/TextInput/BaseTextInput/implementation/index.native.tsx" "react-hooks/refs" 2
"../../src/components/TextInput/BaseTextInput/implementation/index.native.tsx" "react-hooks/static-components" 1
Expand Down Expand Up @@ -491,6 +500,7 @@
"../../src/components/TransactionItemRow/EditableCell/usePopoverEditState.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/UpdateAppModal.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 1
"../../src/components/UploadFile.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/UserInitialsAvatar.tsx" "@typescript-eslint/no-deprecated/fontSize" 1
"../../src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx" "no-restricted-imports" 1
"../../src/components/VideoPlayer/BaseVideoPlayer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 7
"../../src/components/VideoPlayer/BaseVideoPlayer.tsx" "no-restricted-syntax" 1
Expand Down Expand Up @@ -547,6 +557,7 @@
"../../src/hooks/useLazyAsset.ts" "@typescript-eslint/no-unsafe-type-assertion" 3
"../../src/hooks/useLazyAsset.ts" "react-hooks/set-state-in-effect" 1
"../../src/hooks/useListKeyboardNav.ts" "@typescript-eslint/no-unsafe-type-assertion" 2
"../../src/hooks/useMarkdownStyle.ts" "rulesdir/no-raw-typography" 2
"../../src/hooks/useNativeCamera.ts" "react-hooks/refs" 1
"../../src/hooks/useNewTransactions.ts" "react-hooks/refs" 2
"../../src/hooks/useOnyx.ts" "@typescript-eslint/no-unsafe-type-assertion" 10
Expand Down Expand Up @@ -1046,6 +1057,7 @@
"../../src/pages/MissingPersonalDetails/subPages/Address.tsx" "@typescript-eslint/no-unsafe-type-assertion" 5
"../../src/pages/MissingPersonalDetails/subPages/Address.tsx" "react-hooks/refs" 4
"../../src/pages/MissingPersonalDetails/subPages/PIN.tsx" "no-restricted-imports" 1
"../../src/pages/MultifactorAuthentication/AuthorizeTransactionPage/AuthorizeCardTransactionPreview.tsx" "@typescript-eslint/no-deprecated/fontSize" 2
"../../src/pages/MultifactorAuthentication/AuthorizeTransactionPage/AuthorizeTransactionActions.tsx" "no-restricted-imports" 1
"../../src/pages/MultifactorAuthentication/RevokePage.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 1
"../../src/pages/MultifactorAuthentication/RevokePage.tsx" "no-restricted-imports" 1
Expand Down Expand Up @@ -1123,11 +1135,13 @@
"../../src/pages/ShareCodePage.tsx" "@typescript-eslint/no-deprecated/deprecatedGetReportName" 1
"../../src/pages/ShareCodePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2
"../../src/pages/TeachersUnite/SaveTheWorldPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/pages/TransactionDuplicate/Confirmation.tsx" "@typescript-eslint/no-deprecated/fontSize" 1
"../../src/pages/TransactionDuplicate/Confirmation.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/pages/TransactionDuplicate/Confirmation.tsx" "no-restricted-imports" 1
"../../src/pages/TransactionDuplicate/Confirmation.tsx" "react-hooks/refs" 12
"../../src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2
"../../src/pages/TransactionDuplicate/Review.tsx" "no-restricted-imports" 1
"../../src/pages/TransactionDuplicate/ReviewFields.tsx" "@typescript-eslint/no-deprecated/fontSize" 1
"../../src/pages/TransactionMerge/DynamicConfirmationPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/pages/TransactionMerge/DynamicConfirmationPage.tsx" "no-restricted-imports" 1
"../../src/pages/TransactionMerge/DynamicDetailsReviewPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
Expand Down Expand Up @@ -1179,6 +1193,7 @@
"../../src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx" "react-hooks/refs" 30
"../../src/pages/inbox/report/EnableNotificationsBanner.tsx" "no-restricted-imports" 1
"../../src/pages/inbox/report/FloatingMessageCounter.tsx" "no-restricted-imports" 1
"../../src/pages/inbox/report/LinkPreviewer.tsx" "@typescript-eslint/no-deprecated/fontSize" 3
"../../src/pages/inbox/report/ListBoundaryLoader.tsx" "react-hooks/set-state-in-effect" 1
"../../src/pages/inbox/report/ReactionList/HeaderReactionList.tsx" "no-restricted-syntax" 1
"../../src/pages/inbox/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
Expand All @@ -1198,6 +1213,7 @@
"../../src/pages/inbox/report/ReportActionItemMessageEdit.tsx" "react-hooks/preserve-manual-memoization" 1
"../../src/pages/inbox/report/ReportActionItemMessageEdit.tsx" "react-hooks/refs" 5
"../../src/pages/inbox/report/ReportActionItemMessageEdit.tsx" "react-hooks/set-state-in-effect" 1
"../../src/pages/inbox/report/SearchActionHeader.tsx" "@typescript-eslint/no-deprecated/fontSize" 1
"../../src/pages/inbox/report/ShowPreviousMessagesButton.tsx" "no-restricted-imports" 1
"../../src/pages/inbox/report/TripSummary.tsx" "rulesdir/no-default-id-values" 1
"../../src/pages/inbox/report/WhisperBanner.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
Expand All @@ -1207,6 +1223,7 @@
"../../src/pages/inbox/report/actionContents/MemberChangeContent.tsx" "@typescript-eslint/no-deprecated/deprecatedGetReportName" 1
"../../src/pages/inbox/report/actionContents/ReimbursementDeQueuedContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/pages/inbox/report/actionContents/ReimbursementQueuedContent.tsx" "no-restricted-imports" 1
"../../src/pages/inbox/report/comment/TextCommentFragment.tsx" "@typescript-eslint/no-deprecated/fontSize" 1
"../../src/pages/inbox/report/shouldUseEmojiPickerSelection/index.web.ts" "no-restricted-syntax" 1
"../../src/pages/inbox/report/useDebouncedSaveDraft.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/pages/inbox/report/withReportAndPrivateNotesOrNotFound.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
Expand Down Expand Up @@ -1475,6 +1492,7 @@
"../../src/pages/workspace/accounting/xero/advanced/DynamicXeroAccountingMethodPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/pages/workspace/accounting/xero/export/DynamicXeroPurchaseBillDateSelectPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/pages/workspace/accounting/xero/export/DynamicXeroPurchaseBillStatusSelectorPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/pages/workspace/accounting/xero/import/XeroChartOfAccountsPage.tsx" "@typescript-eslint/no-deprecated/fontSize" 1
"../../src/pages/workspace/accounting/xero/import/XeroChartOfAccountsPage.tsx" "no-restricted-syntax" 2
"../../src/pages/workspace/accounting/xero/import/XeroChartOfAccountsPage.tsx" "rulesdir/no-default-id-values" 1
"../../src/pages/workspace/accounting/xero/import/XeroCustomerConfigurationPage.tsx" "no-restricted-imports" 1
Expand Down Expand Up @@ -1562,12 +1580,14 @@
"../../src/stories/ReportActionItemImages.stories.tsx" "no-restricted-syntax" 1
"../../src/stories/TextInput.stories.tsx" "react-hooks/set-state-in-effect" 1
"../../src/styles/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/styles/index.ts" "rulesdir/no-raw-typography" 28
"../../src/styles/theme/utils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/styles/utils/FontUtils/fontFamily/multiFontFamily/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 2
"../../src/styles/utils/autoCompleteSuggestion/index.web.ts" "no-restricted-syntax" 1
"../../src/styles/utils/cursor/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 13
"../../src/styles/utils/generators/TooltipStyleUtils/isOverlappingAtTop/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/styles/utils/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 2
"../../src/styles/utils/index.ts" "rulesdir/no-raw-typography" 4
"../../src/types/form/SpendRuleForm.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/types/onyx/Account.ts" "no-restricted-syntax" 1
"../../src/types/onyx/AccountData.ts" "no-restricted-syntax" 1
Expand Down Expand Up @@ -1960,6 +1980,7 @@
"../../tests/unit/cleanupAndNavigateAfterExpenseCreateTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 9
"../../tests/unit/components/Charts/VictoryTheme.test.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../tests/unit/components/Charts/useChartLabelLayout.test.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../tests/unit/components/HTMLEngineProvider/VictoryChartAttributesParsers.test.ts" "rulesdir/no-raw-typography" 1
"../../tests/unit/components/HTMLEngineProvider/VictoryChartParsers.test.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../tests/unit/components/Image/getImageSource.test.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../tests/unit/components/MoneyRequestConfirmationList/selectorsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
Expand Down
84 changes: 84 additions & 0 deletions eslint-plugin-local-rules/no-raw-typography.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const name = 'no-raw-typography';

const meta = {
type: 'problem',
docs: {
description: 'Disallow raw numeric fontSize/lineHeight values. Type must come from the typography scale so it cannot drift from the design system.',
recommended: 'error',
},
schema: [],
messages: {
rawTypography: 'Raw `{{property}}: {{value}}` is not allowed. Use a `<Text variant="...">` or a token from src/styles/typography.ts (https://github.com/Expensify/App/issues/37503).',
},
};

const BANNED_PROPERTIES = new Set(['fontSize', 'lineHeight']);

/**
* @param {import('estree').Node} key
* @returns {string | undefined}
*/
function getPropertyName(key) {
if (key.type === 'Identifier') {
return key.name;
}
if (key.type === 'Literal' && typeof key.value === 'string') {
return key.value;
}
return undefined;
}

/**
* @param {import('estree').Node} node
* @returns {boolean}
*/
function isNumericLiteral(node) {
if (node.type === 'Literal' && typeof node.value === 'number') {
return true;
}
return node.type === 'UnaryExpression' && node.operator === '-' && isNumericLiteral(node.argument);
}

/**
* Flags object properties (`{fontSize: 17}`) and JSX attributes (`<Text fontSize={17}>`) that set
* `fontSize`/`lineHeight` to a numeric literal. References to tokens or computed values are allowed.
*
* @param {import('eslint').Rule.RuleContext} context
* @returns {import('eslint').Rule.RuleListener}
*/
function create(context) {
function report(valueNode, propertyName) {
context.report({
node: valueNode,
messageId: 'rawTypography',
data: {
property: propertyName,
value: context.sourceCode.getText(valueNode),
},
});
}

return {
Property(node) {
if (node.computed) {
return;
}
const propertyName = getPropertyName(node.key);
if (propertyName === undefined || !BANNED_PROPERTIES.has(propertyName) || !isNumericLiteral(node.value)) {
return;
}
report(node.value, propertyName);
},
JSXAttribute(node) {
if (node.name.type !== 'JSXIdentifier' || !BANNED_PROPERTIES.has(node.name.name)) {
return;
}
if (node.value?.type !== 'JSXExpressionContainer' || !isNumericLiteral(node.value.expression)) {
return;
}
report(node.value.expression, node.name.name);
},
};
}

export {name, meta, create};
Loading
Loading