Skip to content

Commit b6c7ae5

Browse files
chore: Enable and lint-fix TiCS CS rules MAASENG-4719 (#5689)
1 parent 5d8c2fc commit b6c7ae5

File tree

484 files changed

+2867
-2016
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

484 files changed

+2867
-2016
lines changed

.github/workflows/coverage.yaml

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,34 @@
11
name: Test coverage and TiCS Report
22
on:
3-
schedule: # uses last commit on default branch (main)
4-
- cron: "30 0 * * 6" # At 0:30 on Saturday
3+
schedule:
4+
- cron: "30 0 * * 6" # Weekly full analysis
55
push:
66
branches:
77
- "tics-debug-*"
8-
# temporarily disabling client mode due to quota
9-
# pull_request:
10-
# types: [opened, synchronize, reopened]
8+
# We cannot run the TiCS job on pull requests because TiCS
9+
# requires us to use secret tokens, and that cannot be done
10+
# on PRs from forked repositories.
11+
# pull_request:
12+
# types: [ opened, synchronize, reopened ]
1113

1214
jobs:
1315
test-coverage-tics:
1416
name: Run tests with coverage and generate TiCS report
15-
runs-on: ubuntu-22.04
16-
timeout-minutes: 720
17+
runs-on: [ self-hosted, linux, amd64, tiobe, jammy ]
18+
timeout-minutes: 180
1719
steps:
1820
- uses: actions/checkout@v4
19-
20-
- name: Restore node_modules from cache
21-
id: yarn-cache
22-
uses: actions/cache@v4
2321
with:
24-
path: "**/node_modules"
25-
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
22+
fetch-depth: 0 # Important for TICS to analyze history
2623

2724
- name: Setup Node.js from .nvmrc
2825
uses: actions/setup-node@v4
2926
with:
3027
node-version-file: ".nvmrc"
28+
cache: 'yarn'
3129

32-
- name: Install packages if cache is outdated
33-
if: steps.yarn-cache.outputs.cache-hit != 'true'
34-
run: CYPRESS_INSTALL_BINARY=0 yarn install
30+
- name: Install packages
31+
run: CYPRESS_INSTALL_BINARY=0 yarn install --frozen-lockfile
3532

3633
- name: Run tests with coverage
3734
run: yarn test-coverage
@@ -44,22 +41,34 @@ jobs:
4441
path: coverage
4542
retention-days: 1
4643

44+
# Generate a file list for TICS to analyze
45+
- name: Generate file list for TICS
46+
run: |
47+
# Create a list of files to analyze
48+
if [ "${{ github.event_name }}" == "pull_request" ]; then
49+
# For PRs, analyze only changed files
50+
git diff --name-only origin/${{ github.base_ref }} > tics-file-list.txt
51+
else
52+
# For scheduled runs and pushes, analyze the whole project
53+
echo "." > tics-file-list.txt
54+
fi
55+
echo "Files to analyze:"
56+
cat tics-file-list.txt
57+
4758
- name: Run TICS Analyzer
4859
uses: tiobe/tics-github-action@v3
60+
env:
61+
# Add environment variables to optimize TICS performance
62+
TICS_NUM_THREADS: 4 # Adjust based on your runner's capabilities
4963
with:
50-
# Use 'qserver' mode for weekly runs and on push to ci-tics-main to
51-
# generate reference points
52-
# Use 'client' mode for runs on PRs. Client is a quality gate comparing
53-
# changed files to the reference points and can fail if quality degrades
54-
# See:
55-
# https://github.com/tiobe/tics-github-action?tab=readme-ov-file#tics-github-action
5664
mode: ${{ github.event_name == 'pull_request' && 'client' || 'qserver' }}
5765
project: maas-ui
5866
viewerUrl: https://canonical.tiobe.com/tiobeweb/TICS/api/cfg?name=default
5967
ticsAuthToken: ${{ secrets.TICSAUTHTOKEN }}
6068
installTics: true
6169
tmpdir: /tmp/tics
6270
branchdir: ${{ github.workspace }}
71+
filelist: ${{ github.workspace }}/tics-file-list.txt
6372

6473
- name: Upload TICS Report
6574
uses: actions/upload-artifact@v4
@@ -70,15 +79,12 @@ jobs:
7079

7180
publish-coverage-report:
7281
name: Publish Coverage Report
73-
# if: github.ref == 'refs/heads/main'
74-
runs-on: ubuntu-latest
82+
if: github.ref == 'refs/heads/main' || github.event_name == 'schedule'
83+
runs-on: [ self-hosted, linux, amd64, tiobe, jammy ]
7584
needs: test-coverage-tics
76-
continue-on-error: true
77-
7885
permissions:
7986
id-token: write
8087
pages: write
81-
8288
steps:
8389
- uses: actions/checkout@v4
8490

@@ -98,4 +104,4 @@ jobs:
98104

99105
- name: Deploy to GitHub Pages
100106
id: deployment
101-
uses: actions/deploy-pages@v4
107+
uses: actions/deploy-pages@v4

eslint.config.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,85 @@ export default tseslint.config(
216216
"react/no-multi-comp": ["off"],
217217
},
218218
},
219+
{
220+
files: ["src/app/**/*.ts?(x)"],
221+
222+
languageOptions: {
223+
parserOptions: {
224+
project: "./tsconfig.json",
225+
},
226+
},
227+
228+
// The commented-out rules below are checked for code quality by TiCS.
229+
// Currently, these rules have errors that are challenging to fix;
230+
// therefore, they should only be enabled after all corresponding errors
231+
// are resolved.
232+
rules: {
233+
"@typescript-eslint/array-type": "error",
234+
// "@typescript-eslint/class-methods-use-this": "error",
235+
"@typescript-eslint/consistent-indexed-object-style": "error",
236+
// "@typescript-eslint/consistent-return": "error",
237+
// "@typescript-eslint/consistent-type-definitions": "error",
238+
// "@typescript-eslint/consistent-type-exports": "error",
239+
"@typescript-eslint/dot-notation": "error",
240+
// "@typescript-eslint/explicit-function-return-type": "error",
241+
// "@typescript-eslint/explicit-member-accessibility": "error",
242+
// "@typescript-eslint/explicit-module-boundary-types": "error",
243+
// "@typescript-eslint/init-declarations": "error",
244+
// "@typescript-eslint/max-params": [
245+
// "error",
246+
// {
247+
// max: 3,
248+
// },
249+
// ],
250+
// "@typescript-eslint/no-confusing-void-expression": "error",
251+
"@typescript-eslint/no-duplicate-type-constituents": "error",
252+
// "@typescript-eslint/no-dynamic-delete": "error",
253+
// "@typescript-eslint/no-empty-function": "error",
254+
"@typescript-eslint/no-explicit-any": "error",
255+
// "@typescript-eslint/no-floating-promises": "error",
256+
"@typescript-eslint/no-for-in-array": "error",
257+
"@typescript-eslint/no-import-type-side-effects": "error",
258+
"@typescript-eslint/no-inferrable-types": "error",
259+
// "@typescript-eslint/no-invalid-void-type": "error",
260+
// "@typescript-eslint/no-non-null-assertion": "error",
261+
// "@typescript-eslint/no-redeclare": "error",
262+
// "@typescript-eslint/no-redundant-type-constituents": "error",
263+
// "@typescript-eslint/no-shadow": "error",
264+
// "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error",
265+
// "@typescript-eslint/no-unnecessary-condition": "error",
266+
"@typescript-eslint/no-unnecessary-type-arguments": "error",
267+
// "@typescript-eslint/no-unnecessary-type-assertion": "error",
268+
// "@typescript-eslint/no-unsafe-argument": "error",
269+
// "@typescript-eslint/no-unsafe-assignment": "error",
270+
// "@typescript-eslint/no-unsafe-call": "error",
271+
// "@typescript-eslint/no-unsafe-enum-comparison": "error",
272+
// "@typescript-eslint/no-unsafe-member-access": "error",
273+
// "@typescript-eslint/no-unsafe-return": "error",
274+
"@typescript-eslint/no-unused-expressions": "error",
275+
// "@typescript-eslint/no-use-before-define": "error",
276+
// "@typescript-eslint/non-nullable-type-assertion-style": "error",
277+
// "@typescript-eslint/prefer-destructuring": "error",
278+
// "@typescript-eslint/prefer-enum-initializers": "error",
279+
// "@typescript-eslint/prefer-find": "error",
280+
// "@typescript-eslint/prefer-for-of": "error",
281+
"@typescript-eslint/prefer-includes": "error",
282+
// "@typescript-eslint/prefer-literal-enum-member": "error",
283+
// "@typescript-eslint/prefer-nullish-coalescing": "error",
284+
// "@typescript-eslint/prefer-optional-chain": "error",
285+
// "@typescript-eslint/prefer-promise-reject-errors": "error",
286+
"@typescript-eslint/prefer-reduce-type-parameter": "error",
287+
"@typescript-eslint/prefer-regexp-exec": "error",
288+
// "@typescript-eslint/prefer-ts-expect-error": "error",
289+
// "@typescript-eslint/promise-function-async": "error",
290+
// "@typescript-eslint/require-array-sort-compare": "error",
291+
"@typescript-eslint/restrict-plus-operands": "error",
292+
// "@typescript-eslint/restrict-template-expressions": "error",
293+
"@typescript-eslint/sort-type-constituents": "error",
294+
// "@typescript-eslint/strict-boolean-expressions": "error",
295+
// "@typescript-eslint/switch-exhaustiveness-check": "error",
296+
},
297+
},
219298
{
220299
files: ["src/app/apiclient/**/*.[jt]s?(x)"],
221300
rules: {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"cypress-run": "yarn cypress run",
2424
"generate-api-client": "openapi-ts",
2525
"lint": "npmPkgJsonLint . && eslint src cypress && tsc --project tsconfig.json --noEmit && tsc --project cypress/tsconfig.json --noEmit",
26+
"lint-fix": "npmPkgJsonLint . && eslint src cypress --fix && tsc --project tsconfig.json --noEmit && tsc --project cypress/tsconfig.json --noEmit",
2627
"link-components": "yarn link \"@canonical/react-components\" && yarn link \"react\" && yarn install",
2728
"percy": "./cypress/percy.sh",
2829
"release": "yarn clean && yarn install && CI=true yarn test && yarn build && yarn version --new-version",

src/app/api/base.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const DEFAULT_HEADERS = {
1010
Accept: "application/json",
1111
};
1212

13-
export const handleErrors = (response: Response) => {
13+
export const handleErrors = (response: Response): Response => {
1414
if (!response.ok) {
1515
throw Error(response.statusText);
1616
}

src/app/api/query-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const realTimeQueryOptions = {
2828
cacheTime: 60 * 1000, // 1 minute
2929
} as const;
3030

31-
export const createQueryClient = () =>
31+
export const createQueryClient = (): QueryClient =>
3232
new QueryClient({
3333
defaultOptions: {
3434
queries: defaultQueryOptions,

src/app/api/query/pools.test.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,28 @@ const mockServer = setupMockServer(
2626
describe("usePools", () => {
2727
it("should return resource pools data", async () => {
2828
const { result } = renderHookWithProviders(() => usePools());
29-
await waitFor(() => expect(result.current.isSuccess).toBe(true));
29+
await waitFor(() => {
30+
expect(result.current.isSuccess).toBe(true);
31+
});
3032
expect(result.current.data?.items).toEqual(mockPools.items);
3133
});
3234
});
3335

3436
describe("usePoolCount", () => {
3537
it("should return correct count", async () => {
3638
const { result } = renderHookWithProviders(() => usePoolCount());
37-
await waitFor(() => expect(result.current.isSuccess).toBe(true));
39+
await waitFor(() => {
40+
expect(result.current.isSuccess).toBe(true);
41+
});
3842
expect(result.current.data).toBe(3);
3943
});
4044

4145
it("should return 0 when no pools exist", async () => {
4246
mockServer.use(poolsResolvers.listPools.handler({ items: [], total: 0 }));
4347
const { result } = renderHookWithProviders(() => usePoolCount());
44-
await waitFor(() => expect(result.current.isSuccess).toBe(true));
48+
await waitFor(() => {
49+
expect(result.current.isSuccess).toBe(true);
50+
});
4551
expect(result.current.data).toBe(0);
4652
});
4753
});
@@ -54,7 +60,9 @@ describe("useCreatePool", () => {
5460
};
5561
const { result } = renderHookWithProviders(() => useCreatePool());
5662
result.current.mutate({ body: newPool });
57-
await waitFor(() => expect(result.current.isSuccess).toBe(true));
63+
await waitFor(() => {
64+
expect(result.current.isSuccess).toBe(true);
65+
});
5866
});
5967
});
6068

@@ -64,15 +72,19 @@ describe("useGetPool", () => {
6472
const { result } = renderHookWithProviders(() =>
6573
useGetPool({ path: { resource_pool_id: expectedPool.id } })
6674
);
67-
await waitFor(() => expect(result.current.isSuccess).toBe(true));
75+
await waitFor(() => {
76+
expect(result.current.isSuccess).toBe(true);
77+
});
6878
expect(result.current.data).toEqual(expectedPool);
6979
});
7080

7181
it("should return error if zone does not exist", async () => {
7282
const { result } = renderHookWithProviders(() =>
7383
useGetPool({ path: { resource_pool_id: 99 } })
7484
);
75-
await waitFor(() => expect(result.current.isError).toBe(true));
85+
await waitFor(() => {
86+
expect(result.current.isError).toBe(true);
87+
});
7688
});
7789
});
7890

@@ -84,14 +96,18 @@ describe("useUpdatePool", () => {
8496
};
8597
const { result } = renderHookWithProviders(() => useUpdatePool());
8698
result.current.mutate({ body: newPool, path: { resource_pool_id: 1 } });
87-
await waitFor(() => expect(result.current.isSuccess).toBe(true));
99+
await waitFor(() => {
100+
expect(result.current.isSuccess).toBe(true);
101+
});
88102
});
89103
});
90104

91105
describe("useDeletePool", () => {
92106
it("should delete a pool", async () => {
93107
const { result } = renderHookWithProviders(() => useDeletePool());
94108
result.current.mutate({ path: { resource_pool_id: 1 } });
95-
await waitFor(() => expect(result.current.isSuccess).toBe(true));
109+
await waitFor(() => {
110+
expect(result.current.isSuccess).toBe(true);
111+
});
96112
});
97113
});

src/app/api/query/sshKeys.test.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ setupMockServer(
2626
describe("useListSshKeys", () => {
2727
it("should return SSH keys data", async () => {
2828
const { result } = renderHookWithProviders(() => useListSshKeys());
29-
await waitFor(() => expect(result.current.isSuccess).toBe(true));
29+
await waitFor(() => {
30+
expect(result.current.isSuccess).toBe(true);
31+
});
3032
expect(result.current.data).toEqual(mockSshKeys);
3133
});
3234
});
@@ -38,7 +40,9 @@ describe("useCreateSshKeys", () => {
3840
};
3941
const { result } = renderHookWithProviders(() => useCreateSshKeys());
4042
result.current.mutate({ body: newSshKey });
41-
await waitFor(() => expect(result.current.isSuccess).toBe(true));
43+
await waitFor(() => {
44+
expect(result.current.isSuccess).toBe(true);
45+
});
4246
});
4347
});
4448

@@ -50,14 +54,18 @@ describe("useImportSshKeys", () => {
5054
};
5155
const { result } = renderHookWithProviders(() => useImportSshKeys());
5256
result.current.mutate({ body: newSshKey });
53-
await waitFor(() => expect(result.current.isSuccess).toBe(true));
57+
await waitFor(() => {
58+
expect(result.current.isSuccess).toBe(true);
59+
});
5460
});
5561
});
5662

5763
describe("useDeleteSshKey", () => {
5864
it("should delete an SSH key", async () => {
5965
const { result } = renderHookWithProviders(() => useDeleteSshKey());
6066
result.current.mutate({ path: { id: 1 } });
61-
await waitFor(() => expect(result.current.isSuccess).toBe(true));
67+
await waitFor(() => {
68+
expect(result.current.isSuccess).toBe(true);
69+
});
6270
});
6371
});

src/app/api/query/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* @returns {function(T[] | undefined): number} A function that takes an array of items and returns the count of items.
55
*/
66
export const selectItemsCount = <T>() => {
7-
return (data: T[] | undefined) => data?.length ?? 0;
7+
return (data: T[] | undefined): number => data?.length ?? 0;
88
};
99

1010
/**
@@ -16,5 +16,5 @@ export const selectItemsCount = <T>() => {
1616
export const selectById = <T extends { id: number | null }>(
1717
id: number | null
1818
) => {
19-
return (data: T[]) => data.find((item) => item.id === id) || null;
19+
return (data: T[]): T | null => data.find((item) => item.id === id) || null;
2020
};

0 commit comments

Comments
 (0)