Skip to content
Merged
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: 6 additions & 3 deletions .github/workflows/typecheck_build.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Typecheck and Build
name: Lint, Typecheck, and Build

on:
pull_request:
Expand All @@ -10,8 +10,8 @@ on:
workflow_dispatch:

jobs:
typecheck:
name: Typecheck
checks:
name: Lint, Typecheck, and Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -29,3 +29,6 @@ jobs:

- name: Run build
run: yarn build

- name: Run lint
run: yarn lint
4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

yarn lint-staged
2 changes: 1 addition & 1 deletion e2e-tests/elementView.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test';
import type { Page, Locator } from '@playwright/test'
import type { Page, Locator } from '@playwright/test';
import { beforeTest } from './common';

test.beforeEach(beforeTest);
Expand Down
2 changes: 1 addition & 1 deletion e2e-tests/embed.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import { beforeTest } from './common';
import { RightSidebar } from '@visdesignlab/upset2-core';
import { beforeTest } from './common';

test.beforeEach(beforeTest);

Expand Down
17 changes: 7 additions & 10 deletions e2e-tests/plot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,13 @@ test('SVG download includes foreignObject content and preserves hidden bookmark
}

const doc = new DOMParser().parseFromString(svgText, 'image/svg+xml');
const hiddenMuiIconCount = Array.from(
doc.querySelectorAll('svg.MuiSvgIcon-root'),
).filter((icon) =>
/(?:opacity|fill-opacity):\s*0(?:;|$)/.test(icon.getAttribute('style') ?? ''),
).length;
const hiddenMuiIconCount = Array
.from(doc.querySelectorAll('svg.MuiSvgIcon-root'))
.filter((icon) => /(?:opacity|fill-opacity):\s*0(?:;|$)/.test(icon.getAttribute('style') ?? ''))
.length;
const bloatedSvgNodeCount = Array.from(doc.querySelectorAll('[style]')).filter(
(element) => {
if (
element.closest('foreignObject') ||
element.classList.contains('MuiSvgIcon-root')
) {
if (element.closest('foreignObject') || element.classList.contains('MuiSvgIcon-root')) {
return false;
}

Expand Down Expand Up @@ -174,11 +170,12 @@ test('Size header', async ({ page, browserName }) => {
await toggleAdvancedScale(page);
// This legacy code is supposed to move the advanced scale slider but has been broken in Chromium by updates;
// leaving it in case fixing becomes a priority
if (browserName !== 'chromium')
if (browserName !== 'chromium') {
await page
.locator('g.sliding-scale > g.slider-knob')
.locator('rect')
.dragTo(page.getByText('15'), { force: true });
}

const correctMax = browserName === 'chromium' ? 5 : 15;
await assertSizeScaleMax(page, correctMax);
Expand Down
4 changes: 1 addition & 3 deletions e2e-tests/provenance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,6 @@ test('Collapse All Button', async ({ page }) => {
await expect(page.locator('#upset-svg g[aria-label="Expand All"]').first()).toHaveCount(1);

expect(
consoleErrors.some((message) =>
message.includes("can't convert undefined to object"),
),
consoleErrors.some((message) => message.includes("can't convert undefined to object")),
).toBeFalsy();
});
2 changes: 1 addition & 1 deletion e2e-tests/sort.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ async function compareSortedElements(page: Page, order: string[]) {

const res = (await Promise.all(gElements.map((gElement) => gElement.innerHTML()))).slice(0, 5);

for (let i = 0; i < order.length; i++) {
for (let i = 0; i < order.length; i += 1) {
expect(res[i]).toContain(order[i]);
}
}
Expand Down
248 changes: 156 additions & 92 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,103 +1,167 @@
import js from '@eslint/js';
import { fixupConfigRules, fixupPluginRules } from '@eslint/compat';
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import react from 'eslint-plugin-react';
import globals from 'globals';
import tsParser from '@typescript-eslint/parser';
import tsPlugin from '@typescript-eslint/eslint-plugin';
import pluginReact from 'eslint-plugin-react';
import importPlugin from 'eslint-plugin-import';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import js from '@eslint/js';
import { FlatCompat } from '@eslint/eslintrc';

export default [
// Configs further down the array will override rules from earlier configs
js.configs.recommended,
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
project: ['./tsconfig.json', './packages/*/tsconfig.json'],
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const typedFiles = ['**/*.{ts,tsx}'];
const userdocsAliases = [
'@docusaurus/Link',
'@docusaurus/useDocusaurusContext',
'@site/src/components/HomepageFeatures',
'@theme/Heading',
'@theme/Layout',
];
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});

export default [{
ignores: [
'**/.docusaurus/**',
'**/.turbo/**',
'**/build/**',
'**/dist/**',
'**/*.{js,mjs,cjs,jsx}',
'**/node_modules/**',
'**/public/**',
'**/src/public/**',
'**/static/**',
],
}, ...fixupConfigRules(compat.extends(
'airbnb-base',
'airbnb/rules/react',
'plugin:react/recommended',
'eslint:recommended',
'plugin:import/typescript',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
)).map((config) => ({
...config,
files: typedFiles,
})), {
files: typedFiles,
plugins: {
'@typescript-eslint': fixupPluginRules(typescriptEslint),
react: fixupPluginRules(react),
},

languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
ecmaVersion: 'latest',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
globals: globals.browser,
},
plugins: {
'@typescript-eslint': tsPlugin,
sourceType: 'module',
},

settings: {
'import/core-modules': userdocsAliases,
react: {
version: 'detect',
},
rules: {
...tsPlugin.configs.recommended.rules,
'@typescript-eslint/no-unused-vars': [
'warn', // or "error"
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
'import/resolver': {
typescript: {
alwaysTryTypes: true,
noWarnOnMultipleProjects: true,
project: ['./tsconfig.json', './packages/*/tsconfig.json'],
},
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.d.ts'],
},
},
},
pluginReact.configs.flat.recommended,
importPlugin.flatConfigs.recommended,
eslintPluginPrettierRecommended,
{
files: ['**/*.{js,mjs,cjs,jsx}'],
languageOptions: {
globals: globals.browser,
},

rules: {
'class-methods-use-this': 'off',
'linebreak-style': 'off',
'no-restricted-exports': 'off',
'no-restricted-syntax': 'off',
'no-underscore-dangle': 'off',
'no-nested-ternary': 'off',
'no-shadow': 'off',

'no-console': ['error', {
allow: ['warn', 'error'],
}],

'react/no-array-index-key': 'off',
'jsx-a11y/click-events-have-key-events': 'off',
'no-return-await': 'off',

'max-classes-per-file': 'off',

'no-param-reassign': ['error', {
props: false,
}],

'import/no-extraneous-dependencies': 'off',
'import/prefer-default-export': 'off',
'import/order': 'error',

'prefer-destructuring': ['warn', {
object: true,
array: false,
}],

'prefer-promise-reject-errors': 'warn',
'prefer-spread': 'warn',
'react/react-in-jsx-scope': 0,
'max-len': 0,
'react/jsx-filename-extension': 0,
'react/no-unknown-property': ['error', {
ignore: ['css'],
}],
'import/extensions': ['error', 'never', {
json: 'always',
}],
'react/destructuring-assignment': 'off',
'react/jsx-props-no-spreading': 'off',
'react/no-unused-class-component-methods': 'warn',
'react/require-default-props': 'off',

'react/static-property-placement': ['warn', 'property assignment', {
childContextTypes: 'static getter',
contextTypes: 'static public field',
contextType: 'static public field',
displayName: 'static public field',
}],
},
{
plugins: {
'jsx-a11y': jsxA11y,
},
rules: {
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
singleQuote: true,
trailingComma: 'all',
printWidth: 90,
semi: true,
tabWidth: 2,
useTabs: false,
},
],
'react/jsx-filename-extension': [2, { extensions: ['.js', '.jsx', '.ts', '.tsx'] }],
'import/prefer-default-export': 'off',
'react/function-component-definition': 'off',
'no-plusplus': ['warn', { allowForLoopAfterthoughts: true }],
'dot-notation': 'off',
'import/extensions': 'off',
'react/jsx-props-no-spreading': 'off',
'react/require-default-props': 'off',
'react/react-in-jsx-scope': 'off',
'react/no-unknown-property': ['error', { ignore: ['css'] }],
'operator-linebreak': 'off',
// needs to be duplicated because we have this rule in 2 plugins (=
'no-unused-vars': [
'warn', // or "error"
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
'max-len': 'off',
'no-param-reassign': 'off',
'import/no-cycle': 'off',
'no-underscore-dangle': 'off',
'no-nested-ternary': 'off',
'jsx-a11y/tabindex-no-positive': 'off',
'no-bitwise': 'warn',
'no-restricted-syntax': 'warn',
'no-trailing-spaces': 'error',
'import/named': 'off',
'import/no-unresolved': 'off',
'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0, maxBOF: 0 }],
'react/display-name': 'off',
}, {
files: typedFiles,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
];
rules: {
'no-shadow': 'off',
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/no-unused-expressions': ['error', {
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: true,
}],
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
}],
'@typescript-eslint/no-shadow': 'error',
},
}];
Loading
Loading