From 5294adcb86801cb9649d04a30572ee30fb43374c Mon Sep 17 00:00:00 2001 From: Daniil Sapa Date: Wed, 16 Oct 2024 01:53:11 +0300 Subject: [PATCH 01/14] Add a test case for vue file structures for insignificant-slice rule --- .../src/insignificant-slice/index.spec.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/packages/steiger-plugin-fsd/src/insignificant-slice/index.spec.ts b/packages/steiger-plugin-fsd/src/insignificant-slice/index.spec.ts index cf3f819..cc101f5 100644 --- a/packages/steiger-plugin-fsd/src/insignificant-slice/index.spec.ts +++ b/packages/steiger-plugin-fsd/src/insignificant-slice/index.spec.ts @@ -21,14 +21,25 @@ vi.mock('node:fs', async (importOriginal) => { '/shared/ui/Button.tsx': 'import styles from "./styles";', '/shared/ui/TextField.tsx': 'import styles from "./styles";', '/shared/ui/index.ts': '', + '/entities/user/@x/product.ts': '', '/entities/user/ui/UserAvatar.tsx': 'import { Button } from "@/shared/ui"', '/entities/user/index.ts': '', '/entities/product/ui/ProductCard.tsx': '', '/entities/product/ui/CrossReferenceCard.tsx': 'import { UserAvatar } from "@/entities/user/@x/product"', '/entities/product/index.ts': '', + '/entities/post/index.ts': '', + '/entities/post/ui/post-card.vue': '', + '/features/comments/ui/CommentCard.tsx': '', '/features/comments/index.ts': '', + '/features/session/register/index.ts': 'import {RegisterForm} from "./ui/RegisterForm.vue"', + '/features/session/register/ui/RegisterForm.vue': '', + '/features/session/logout/index.ts': 'import {LogoutButton} from "./ui/LogoutButton.vue"', + '/features/session/logout/ui/LogoutButton.vue': '', + '/features/session/login/index.ts': 'import {LoginForm} from "./ui/LoginForm.vue"', + '/features/session/login/ui/LoginForm.vue': '', + '/pages/editor/ui/EditorPage.tsx': 'import { Button } from "@/shared/ui"; import { Editor } from "./Editor"; import { CommentCard } from "@/features/comments"; import { UserAvatar } from "@/entities/user"', '/pages/editor/ui/Editor.tsx': @@ -37,6 +48,12 @@ vi.mock('node:fs', async (importOriginal) => { '/pages/settings/ui/SettingsPage.tsx': 'import { Button } from "@/shared/ui"; import { CommentCard } from "@/features/comments"', '/pages/settings/index.ts': '', + '/pages/home/index.ts': '', + '/pages/home/ui/home.vue': + ' ', + '/pages/category/index.ts': '', + '/pages/category/ui/category.vue': + ' ', }, originalFs, ) @@ -167,3 +184,38 @@ it('reports errors on a project where the only other reference to a slice is a c }, ]) }) + +it('should report no errors on a project with Vue Single-File Components (*.vue files)', async () => { + const root = parseIntoFsdRoot(` + 📂 entities + 📂 post + 📂 ui + 📄 post-card.vue + 📄 index.ts + 📂 features + 📂 session + 📂 login + 📂 ui + LoginForm.vue + 📄 index.ts + 📂 logout + 📂 ui + LogoutButton.vue + 📄 index.ts + 📂 register + 📂 ui + RegisterForm.vue + 📄 index.ts + 📂 pages + 📂 home + 📂 ui + 📄 home.vue + 📄 index.ts + 📂 category + 📂 ui + 📄 category.vue + 📄 index.ts + `) + + expect((await insignificantSlice.check(root)).diagnostics).toEqual([]) +}) From d59d935ed40fe299d2b74de807ca2de1232494c5 Mon Sep 17 00:00:00 2001 From: Daniil Sapa Date: Wed, 16 Oct 2024 01:56:49 +0300 Subject: [PATCH 02/14] Add a changelog --- .changeset/dull-rice-guess.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dull-rice-guess.md diff --git a/.changeset/dull-rice-guess.md b/.changeset/dull-rice-guess.md new file mode 100644 index 0000000..f866262 --- /dev/null +++ b/.changeset/dull-rice-guess.md @@ -0,0 +1,5 @@ +--- +'@feature-sliced/steiger-plugin': patch +--- + +Add a Vue test case for insignificant-slice From cd94bf942a0b1627d8d054b00d519ed23432240c Mon Sep 17 00:00:00 2001 From: Daniil Sapa Date: Tue, 29 Oct 2024 14:27:51 +0200 Subject: [PATCH 03/14] Fix the issue with ts paths in config extensions --- .../src/insignificant-slice/index.ts | 34 +++- .../src/insignificant-slice/nuxt.spec.ts | 164 ++++++++++++++++++ 2 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 packages/steiger-plugin-fsd/src/insignificant-slice/nuxt.spec.ts diff --git a/packages/steiger-plugin-fsd/src/insignificant-slice/index.ts b/packages/steiger-plugin-fsd/src/insignificant-slice/index.ts index 83186b3..278a1d2 100644 --- a/packages/steiger-plugin-fsd/src/insignificant-slice/index.ts +++ b/packages/steiger-plugin-fsd/src/insignificant-slice/index.ts @@ -1,5 +1,6 @@ import * as fs from 'node:fs' import { sep, join } from 'node:path' +import type { CompilerOptions } from 'typescript' import { parse as parseNearestTsConfig } from 'tsconfck' import { isSliced, resolveImport, unslicedLayers, type LayerName } from '@feature-sliced/filesystem' import type { Folder, PartialDiagnostic, Rule } from '@steiger/types' @@ -9,6 +10,11 @@ const { paperwork } = precinct import { indexSourceFiles } from '../_lib/index-source-files.js' import { NAMESPACE } from '../constants.js' +interface TsConfigExcerpts { + extends: string + compilerOptions: CompilerOptions +} + const insignificantSlice = { name: `${NAMESPACE}/insignificant-slice`, async check(root) { @@ -51,7 +57,9 @@ export default insignificantSlice async function traceSliceReferences(root: Folder) { const sourceFileIndex = indexSourceFiles(root) - const { tsconfig } = await parseNearestTsConfig(root.path) + const { tsconfig, tsconfigFile } = await parseNearestTsConfig(root.path) + + const resolvedConfig = resolveRelativePathsInConfigExtensions(tsconfig, tsconfigFile) const references = new Map>() for (const sourceFile of Object.values(sourceFileIndex)) { @@ -62,7 +70,7 @@ async function traceSliceReferences(root: Folder) { const resolvedDependency = resolveImport( dependency, sourceFile.file.path, - tsconfig?.compilerOptions ?? {}, + resolvedConfig?.compilerOptions ?? {}, fs.existsSync, fs.existsSync, ) @@ -93,3 +101,25 @@ async function traceSliceReferences(root: Folder) { return references } + +function resolveRelativePathsInConfigExtensions(tsconfig: TsConfigExcerpts, configPath: string) { + if (!tsconfig || !tsconfig.extends || !tsconfig.compilerOptions) { + return tsconfig + } + + const paths = tsconfig.compilerOptions?.paths ?? {} + + const newPaths = Object.fromEntries( + Object.entries(paths).map(([key, values]) => { + return [key, values.map((value: string) => join(configPath, '..', value))] + }), + ) + + return { + ...tsconfig, + compilerOptions: { + ...tsconfig.compilerOptions, + paths: newPaths, + }, + } +} diff --git a/packages/steiger-plugin-fsd/src/insignificant-slice/nuxt.spec.ts b/packages/steiger-plugin-fsd/src/insignificant-slice/nuxt.spec.ts new file mode 100644 index 0000000..efa7cff --- /dev/null +++ b/packages/steiger-plugin-fsd/src/insignificant-slice/nuxt.spec.ts @@ -0,0 +1,164 @@ +import { expect, it, vi } from 'vitest' + +import { parseIntoFsdRoot } from '../_lib/prepare-test.js' +import insignificantSlice from './index.js' + +vi.mock('tsconfck', async (importOriginal) => { + return { + ...(await importOriginal()), + parse: vi.fn(() => + Promise.resolve({ + tsconfigFile: '/tsconfig.json', + tsconfig: { + extends: './tsconfig.json', + compilerOptions: { + paths: { + nitropack: ['../node_modules/.pnpm/nitropack@2.9.7_magicast@0.3.5/node_modules/nitropack'], + defu: ['../node_modules/.pnpm/defu@6.1.4/node_modules/defu'], + h3: ['../node_modules/.pnpm/h3@1.13.0/node_modules/h3'], + consola: ['../node_modules/.pnpm/consola@3.2.3/node_modules/consola'], + ofetch: ['../node_modules/.pnpm/ofetch@1.4.1/node_modules/ofetch'], + '@unhead/vue': ['../node_modules/.pnpm/@unhead+vue@1.11.10_vue@3.5.12/node_modules/@unhead/vue'], + '@nuxt/devtools': [ + '../node_modules/.pnpm/@nuxt+devtools@1.6.0_rollup@4.24.0_vite@5.4.9_@types+node@22.7.7_terser@5.36.0__vue@3.5.12/node_modules/@nuxt/devtools', + ], + '@vue/runtime-core': ['../node_modules/.pnpm/@vue+runtime-core@3.5.12/node_modules/@vue/runtime-core'], + '@vue/compiler-sfc': ['../node_modules/.pnpm/@vue+compiler-sfc@3.5.12/node_modules/@vue/compiler-sfc'], + 'unplugin-vue-router/client': [ + '../node_modules/.pnpm/unplugin-vue-router@0.10.8_rollup@4.24.0_vue-router@4.4.5_vue@3.5.12__vue@3.5.12/node_modules/unplugin-vue-router/client', + ], + '@nuxt/schema': ['../node_modules/.pnpm/@nuxt+schema@3.13.2_rollup@4.24.0/node_modules/@nuxt/schema'], + nuxt: [ + '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt', + ], + '~': ['..'], + '~/*': ['../*'], + '@': ['..'], + '@/*': ['../*'], + '~~': ['..'], + '~~/*': ['../*'], + '@@': ['..'], + '@@/*': ['../*'], + assets: ['../assets'], + public: ['../public'], + 'public/*': ['../public/*'], + '#app': [ + '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt/dist/app', + ], + '#app/*': [ + '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt/dist/app/*', + ], + 'vue-demi': [ + '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt/dist/app/compat/vue-demi', + ], + '#vue-router': ['../node_modules/.pnpm/vue-router@4.4.5_vue@3.5.12/node_modules/vue-router'], + '#imports': ['./imports'], + '#app-manifest': ['./manifest/meta/dev'], + '#build': ['.'], + '#build/*': ['./*'], + '#components': ['./components'], + }, + esModuleInterop: true, + skipLibCheck: true, + target: 'ESNext', + allowJs: true, + resolveJsonModule: true, + moduleDetection: 'force', + isolatedModules: true, + verbatimModuleSyntax: true, + strict: true, + noUncheckedIndexedAccess: false, + forceConsistentCasingInFileNames: true, + noImplicitOverride: true, + module: 'ESNext', + noEmit: true, + lib: ['ESNext', 'dom', 'dom.iterable', 'webworker'], + jsx: 'preserve', + jsxImportSource: 'vue', + types: [], + moduleResolution: 'Bundler', + useDefineForClassFields: true, + noImplicitThis: true, + allowSyntheticDefaultImports: true, + }, + include: [ + '.nuxt/nuxt.d.ts', + '.config/nuxt.*', + '**/*', + 'node_modules/.pnpm/@nuxt+devtools@1.6.0_rollup@4.24.0_vite@5.4.9_@types+node@22.7.7_terser@5.36.0__vue@3.5.12/node_modules/@nuxt/devtools/runtime', + 'node_modules/.pnpm/@nuxt+devtools@1.6.0_rollup@4.24.0_vite@5.4.9_@types+node@22.7.7_terser@5.36.0__vue@3.5.12/node_modules/@nuxt/devtools/dist/runtime', + 'node_modules/.pnpm/@nuxt+telemetry@2.6.0_magicast@0.3.5_rollup@4.24.0/node_modules/@nuxt/telemetry/runtime', + 'node_modules/.pnpm/@nuxt+telemetry@2.6.0_magicast@0.3.5_rollup@4.24.0/node_modules/@nuxt/telemetry/dist/runtime', + '.', + ], + exclude: [ + 'dist', + 'node_modules', + 'node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt/node_modules', + 'node_modules/.pnpm/@nuxt+devtools@1.6.0_rollup@4.24.0_vite@5.4.9_@types+node@22.7.7_terser@5.36.0__vue@3.5.12/node_modules/@nuxt/devtools/runtime/server', + 'node_modules/.pnpm/@nuxt+devtools@1.6.0_rollup@4.24.0_vite@5.4.9_@types+node@22.7.7_terser@5.36.0__vue@3.5.12/node_modules/@nuxt/devtools/dist/runtime/server', + 'node_modules/.pnpm/@nuxt+telemetry@2.6.0_magicast@0.3.5_rollup@4.24.0/node_modules/@nuxt/telemetry/runtime/server', + 'node_modules/.pnpm/@nuxt+telemetry@2.6.0_magicast@0.3.5_rollup@4.24.0/node_modules/@nuxt/telemetry/dist/runtime/server', + '.output', + ], + }, + }), + ), + } +}) + +vi.mock('node:fs', async (importOriginal) => { + const originalFs = await importOriginal() + const { createFsMocks } = await import('../_lib/prepare-test.js') + + return createFsMocks( + { + '/features/session/register/index.ts': 'import {RegisterForm} from "./ui/RegisterForm.vue"', + '/features/session/register/ui/RegisterForm.vue': '', + + '/features/session/logout/index.ts': 'import {LogoutButton} from "./ui/LogoutButton.vue"', + '/features/session/logout/ui/LogoutButton.vue': '', + + '/features/session/login/index.ts': 'import {LoginForm} from "./ui/LoginForm.vue"', + '/features/session/login/ui/LoginForm.vue': '', + + '/pages/home/index.ts': '', + '/pages/home/ui/home.vue': + ' ', + '/pages/category/index.ts': '', + '/pages/category/ui/category.vue': + ' ', + }, + originalFs, + ) +}) + +it('should report no errors on a project with Vue Single-File Components (*.vue files)', async () => { + const root = parseIntoFsdRoot(` + 📂 features + 📂 session + 📂 login + 📂 ui + LoginForm.vue + 📄 index.ts + 📂 logout + 📂 ui + LogoutButton.vue + 📄 index.ts + 📂 register + 📂 ui + RegisterForm.vue + 📄 index.ts + 📂 pages + 📂 home + 📂 ui + 📄 home.vue + 📄 index.ts + 📂 category + 📂 ui + 📄 category.vue + 📄 index.ts + `) + + expect((await insignificantSlice.check(root)).diagnostics).toEqual([]) +}) From 3170217a237e077142e3a20d1c3482f2149a7a62 Mon Sep 17 00:00:00 2001 From: Daniil Sapa Date: Tue, 29 Oct 2024 14:29:32 +0200 Subject: [PATCH 04/14] Remove a test case for Vue for index.spec --- .../src/insignificant-slice/index.spec.ts | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/packages/steiger-plugin-fsd/src/insignificant-slice/index.spec.ts b/packages/steiger-plugin-fsd/src/insignificant-slice/index.spec.ts index cc101f5..09a7bda 100644 --- a/packages/steiger-plugin-fsd/src/insignificant-slice/index.spec.ts +++ b/packages/steiger-plugin-fsd/src/insignificant-slice/index.spec.ts @@ -29,16 +29,9 @@ vi.mock('node:fs', async (importOriginal) => { '/entities/product/ui/CrossReferenceCard.tsx': 'import { UserAvatar } from "@/entities/user/@x/product"', '/entities/product/index.ts': '', '/entities/post/index.ts': '', - '/entities/post/ui/post-card.vue': '', '/features/comments/ui/CommentCard.tsx': '', '/features/comments/index.ts': '', - '/features/session/register/index.ts': 'import {RegisterForm} from "./ui/RegisterForm.vue"', - '/features/session/register/ui/RegisterForm.vue': '', - '/features/session/logout/index.ts': 'import {LogoutButton} from "./ui/LogoutButton.vue"', - '/features/session/logout/ui/LogoutButton.vue': '', - '/features/session/login/index.ts': 'import {LoginForm} from "./ui/LoginForm.vue"', - '/features/session/login/ui/LoginForm.vue': '', '/pages/editor/ui/EditorPage.tsx': 'import { Button } from "@/shared/ui"; import { Editor } from "./Editor"; import { CommentCard } from "@/features/comments"; import { UserAvatar } from "@/entities/user"', @@ -49,11 +42,7 @@ vi.mock('node:fs', async (importOriginal) => { 'import { Button } from "@/shared/ui"; import { CommentCard } from "@/features/comments"', '/pages/settings/index.ts': '', '/pages/home/index.ts': '', - '/pages/home/ui/home.vue': - ' ', '/pages/category/index.ts': '', - '/pages/category/ui/category.vue': - ' ', }, originalFs, ) @@ -184,38 +173,3 @@ it('reports errors on a project where the only other reference to a slice is a c }, ]) }) - -it('should report no errors on a project with Vue Single-File Components (*.vue files)', async () => { - const root = parseIntoFsdRoot(` - 📂 entities - 📂 post - 📂 ui - 📄 post-card.vue - 📄 index.ts - 📂 features - 📂 session - 📂 login - 📂 ui - LoginForm.vue - 📄 index.ts - 📂 logout - 📂 ui - LogoutButton.vue - 📄 index.ts - 📂 register - 📂 ui - RegisterForm.vue - 📄 index.ts - 📂 pages - 📂 home - 📂 ui - 📄 home.vue - 📄 index.ts - 📂 category - 📂 ui - 📄 category.vue - 📄 index.ts - `) - - expect((await insignificantSlice.check(root)).diagnostics).toEqual([]) -}) From 7327f0e0098489b350ee43e4b2334ba79970041b Mon Sep 17 00:00:00 2001 From: Daniil Sapa Date: Tue, 5 Nov 2024 15:26:15 +0200 Subject: [PATCH 05/14] Move the solution to collect-related-ts-configs, refactor it --- .../src/_lib/collect-related-ts-configs.ts | 75 +++++++++++- .../src/insignificant-slice/index.ts | 26 +---- .../src/insignificant-slice/nuxt.spec.ts | 109 ++++++++++++++---- 3 files changed, 160 insertions(+), 50 deletions(-) diff --git a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts index 4121a13..d4263e7 100644 --- a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts +++ b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts @@ -1,8 +1,12 @@ import { TSConfckParseResult } from 'tsconfck' +import { dirname, join } from 'node:path' +import path from 'node:path' export type CollectRelatedTsConfigsPayload = { tsconfig: TSConfckParseResult['tsconfig'] + tsconfigFile?: TSConfckParseResult['tsconfigFile'] referenced?: CollectRelatedTsConfigsPayload[] + extended?: TSConfckParseResult[] } /** @@ -46,8 +50,8 @@ export type CollectRelatedTsConfigsPayload = { export function collectRelatedTsConfigs(payload: CollectRelatedTsConfigsPayload) { const configs: Array = [] - const setTsConfigs = ({ tsconfig, referenced }: CollectRelatedTsConfigsPayload) => { - configs.push(tsconfig) + const setTsConfigs = ({ tsconfig, tsconfigFile, referenced, extended }: CollectRelatedTsConfigsPayload) => { + configs.push(resolveRelativePathsInMergedConfig({ tsconfig, tsconfigFile, extended })) referenced?.forEach(setTsConfigs) } setTsConfigs(payload) @@ -55,6 +59,73 @@ export function collectRelatedTsConfigs(payload: CollectRelatedTsConfigsPayload) return configs } +// As tsconfck does not resolve paths in merged configs, +// namely it just takes paths from extended config and puts them to the final merged config we need to do it manually. +// (If some extended config is nested in a folder e.g. ./nuxt/tsconfig.json, +// it applies path aliases like '@/': ['../*'] to the project root) +function resolveRelativePathsInMergedConfig(configParseResult: CollectRelatedTsConfigsPayload) { + function findFirstConfigWithPaths( + parseResult: CollectRelatedTsConfigsPayload, + ): CollectRelatedTsConfigsPayload | null { + if (parseResult.tsconfig.compilerOptions?.paths || !parseResult.tsconfigFile) { + return parseResult + } + + const extendAbsolutePath = join(dirname(parseResult.tsconfigFile), parseResult.tsconfig.extends) + const extendedConfig = (extended || []).find(({ tsconfigFile }) => tsconfigFile === extendAbsolutePath) + + return findFirstConfigWithPaths(extendedConfig!) + } + + function turnPathAliasesIntoAbsolute( + finalConfig: CollectRelatedTsConfigsPayload, + firstConfigWithPaths: CollectRelatedTsConfigsPayload, + ) { + const { tsconfig: mergedConfig } = finalConfig + const absolutePaths: Record> = {} + + if (!firstConfigWithPaths.tsconfigFile) { + return mergedConfig.compilerOptions.paths + } + + for (const entries of Object.entries(mergedConfig.compilerOptions.paths)) { + const [key, paths] = entries as [key: string, paths: Array] + absolutePaths[key] = paths.map((relativePath: string) => + path.resolve(path.dirname(firstConfigWithPaths.tsconfigFile!), relativePath), + ) + } + + return absolutePaths + } + + const { tsconfig: mergedConfig, extended } = configParseResult + + if ( + // if there's no config it needs to be handled somewhere else + !mergedConfig || + // if the merged config does not have "extends" there are no paths to resolve + !mergedConfig.extends || + // if the merged config does not have compilerOptions, there is nothing to resolve + !mergedConfig.compilerOptions || + // if the merged config does not have paths in compilerOptions there's nothing to resolve + !mergedConfig.compilerOptions.paths + ) { + return mergedConfig + } + + // Find the first config with paths in the "extends" chain as it overrides the others + const firstConfigWithPaths = findFirstConfigWithPaths(configParseResult) + const absolutePaths = turnPathAliasesIntoAbsolute(configParseResult, firstConfigWithPaths!) + + return { + ...mergedConfig, + compilerOptions: { + ...mergedConfig.compilerOptions, + paths: absolutePaths, + }, + } +} + if (import.meta.vitest) { const { test, expect } = import.meta.vitest diff --git a/packages/steiger-plugin-fsd/src/insignificant-slice/index.ts b/packages/steiger-plugin-fsd/src/insignificant-slice/index.ts index 6766d2f..393fede 100644 --- a/packages/steiger-plugin-fsd/src/insignificant-slice/index.ts +++ b/packages/steiger-plugin-fsd/src/insignificant-slice/index.ts @@ -53,8 +53,8 @@ export default insignificantSlice async function traceSliceReferences(root: Folder) { const sourceFileIndex = indexSourceFiles(root) - const { tsconfig, referenced } = await parseNearestTsConfig(root.path) - const tsConfigs = collectRelatedTsConfigs({ tsconfig, referenced }) + const parseResult = await parseNearestTsConfig(root.path) + const tsConfigs = collectRelatedTsConfigs(parseResult) const references = new Map>() for (const sourceFile of Object.values(sourceFileIndex)) { @@ -97,25 +97,3 @@ async function traceSliceReferences(root: Folder) { return references } - -// function resolveRelativePathsInConfigExtensions(tsconfig: TsConfigExcerpts, configPath: string) { -// if (!tsconfig || !tsconfig.extends || !tsconfig.compilerOptions) { -// return tsconfig -// } -// -// const paths = tsconfig.compilerOptions?.paths ?? {} -// -// const newPaths = Object.fromEntries( -// Object.entries(paths).map(([key, values]) => { -// return [key, values.map((value: string) => join(configPath, '..', value))] -// }), -// ) -// -// return { -// ...tsconfig, -// compilerOptions: { -// ...tsconfig.compilerOptions, -// paths: newPaths, -// }, -// } -// } diff --git a/packages/steiger-plugin-fsd/src/insignificant-slice/nuxt.spec.ts b/packages/steiger-plugin-fsd/src/insignificant-slice/nuxt.spec.ts index efa7cff..4982d67 100644 --- a/packages/steiger-plugin-fsd/src/insignificant-slice/nuxt.spec.ts +++ b/packages/steiger-plugin-fsd/src/insignificant-slice/nuxt.spec.ts @@ -1,6 +1,6 @@ import { expect, it, vi } from 'vitest' -import { parseIntoFsdRoot } from '../_lib/prepare-test.js' +import { parseIntoFolder } from '@steiger/toolkit' import insignificantSlice from './index.js' vi.mock('tsconfck', async (importOriginal) => { @@ -8,9 +8,90 @@ vi.mock('tsconfck', async (importOriginal) => { ...(await importOriginal()), parse: vi.fn(() => Promise.resolve({ + extended: [ + { + tsconfigFile: '/tsconfig.json', + tsconfig: { + compilerOptions: { + paths: { + nitropack: ['../node_modules/.pnpm/nitropack@2.9.7_magicast@0.3.5/node_modules/nitropack'], + defu: ['../node_modules/.pnpm/defu@6.1.4/node_modules/defu'], + h3: ['../node_modules/.pnpm/h3@1.13.0/node_modules/h3'], + consola: ['../node_modules/.pnpm/consola@3.2.3/node_modules/consola'], + ofetch: ['../node_modules/.pnpm/ofetch@1.4.1/node_modules/ofetch'], + '@unhead/vue': ['../node_modules/.pnpm/@unhead+vue@1.11.10_vue@3.5.12/node_modules/@unhead/vue'], + '@nuxt/devtools': [ + '../node_modules/.pnpm/@nuxt+devtools@1.6.0_rollup@4.24.0_vite@5.4.9_@types+node@22.7.7_terser@5.36.0__vue@3.5.12/node_modules/@nuxt/devtools', + ], + '@vue/runtime-core': [ + '../node_modules/.pnpm/@vue+runtime-core@3.5.12/node_modules/@vue/runtime-core', + ], + '@vue/compiler-sfc': [ + '../node_modules/.pnpm/@vue+compiler-sfc@3.5.12/node_modules/@vue/compiler-sfc', + ], + 'unplugin-vue-router/client': [ + '../node_modules/.pnpm/unplugin-vue-router@0.10.8_rollup@4.24.0_vue-router@4.4.5_vue@3.5.12__vue@3.5.12/node_modules/unplugin-vue-router/client', + ], + '@nuxt/schema': ['../node_modules/.pnpm/@nuxt+schema@3.13.2_rollup@4.24.0/node_modules/@nuxt/schema'], + nuxt: [ + '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt', + ], + '~': ['..'], + '~/*': ['../*'], + '@': ['..'], + '@/*': ['../*'], + '~~': ['..'], + '~~/*': ['../*'], + '@@': ['..'], + '@@/*': ['../*'], + assets: ['../assets'], + public: ['../public'], + 'public/*': ['../public/*'], + '#app': [ + '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt/dist/app', + ], + '#app/*': [ + '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt/dist/app/*', + ], + 'vue-demi': [ + '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt/dist/app/compat/vue-demi', + ], + '#vue-router': ['../node_modules/.pnpm/vue-router@4.4.5_vue@3.5.12/node_modules/vue-router'], + '#imports': ['./imports'], + '#app-manifest': ['./manifest/meta/dev'], + '#build': ['.'], + '#build/*': ['./*'], + '#components': ['./components'], + }, + esModuleInterop: true, + skipLibCheck: true, + target: 'ESNext', + allowJs: true, + resolveJsonModule: true, + moduleDetection: 'force', + isolatedModules: true, + verbatimModuleSyntax: true, + strict: true, + noUncheckedIndexedAccess: false, + forceConsistentCasingInFileNames: true, + noImplicitOverride: true, + module: 'ESNext', + noEmit: true, + lib: ['ESNext', 'dom', 'dom.iterable', 'webworker'], + jsx: 'preserve', + jsxImportSource: 'vue', + types: [], + moduleResolution: 'Bundler', + useDefineForClassFields: true, + noImplicitThis: true, + allowSyntheticDefaultImports: true, + }, + }, + }, + ], tsconfigFile: '/tsconfig.json', tsconfig: { - extends: './tsconfig.json', + extends: './nuxt/tsconfig.json', compilerOptions: { paths: { nitropack: ['../node_modules/.pnpm/nitropack@2.9.7_magicast@0.3.5/node_modules/nitropack'], @@ -81,26 +162,6 @@ vi.mock('tsconfck', async (importOriginal) => { noImplicitThis: true, allowSyntheticDefaultImports: true, }, - include: [ - '.nuxt/nuxt.d.ts', - '.config/nuxt.*', - '**/*', - 'node_modules/.pnpm/@nuxt+devtools@1.6.0_rollup@4.24.0_vite@5.4.9_@types+node@22.7.7_terser@5.36.0__vue@3.5.12/node_modules/@nuxt/devtools/runtime', - 'node_modules/.pnpm/@nuxt+devtools@1.6.0_rollup@4.24.0_vite@5.4.9_@types+node@22.7.7_terser@5.36.0__vue@3.5.12/node_modules/@nuxt/devtools/dist/runtime', - 'node_modules/.pnpm/@nuxt+telemetry@2.6.0_magicast@0.3.5_rollup@4.24.0/node_modules/@nuxt/telemetry/runtime', - 'node_modules/.pnpm/@nuxt+telemetry@2.6.0_magicast@0.3.5_rollup@4.24.0/node_modules/@nuxt/telemetry/dist/runtime', - '.', - ], - exclude: [ - 'dist', - 'node_modules', - 'node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt/node_modules', - 'node_modules/.pnpm/@nuxt+devtools@1.6.0_rollup@4.24.0_vite@5.4.9_@types+node@22.7.7_terser@5.36.0__vue@3.5.12/node_modules/@nuxt/devtools/runtime/server', - 'node_modules/.pnpm/@nuxt+devtools@1.6.0_rollup@4.24.0_vite@5.4.9_@types+node@22.7.7_terser@5.36.0__vue@3.5.12/node_modules/@nuxt/devtools/dist/runtime/server', - 'node_modules/.pnpm/@nuxt+telemetry@2.6.0_magicast@0.3.5_rollup@4.24.0/node_modules/@nuxt/telemetry/runtime/server', - 'node_modules/.pnpm/@nuxt+telemetry@2.6.0_magicast@0.3.5_rollup@4.24.0/node_modules/@nuxt/telemetry/dist/runtime/server', - '.output', - ], }, }), ), @@ -109,7 +170,7 @@ vi.mock('tsconfck', async (importOriginal) => { vi.mock('node:fs', async (importOriginal) => { const originalFs = await importOriginal() - const { createFsMocks } = await import('../_lib/prepare-test.js') + const { createFsMocks } = await import('@steiger/toolkit') return createFsMocks( { @@ -134,7 +195,7 @@ vi.mock('node:fs', async (importOriginal) => { }) it('should report no errors on a project with Vue Single-File Components (*.vue files)', async () => { - const root = parseIntoFsdRoot(` + const root = parseIntoFolder(` 📂 features 📂 session 📂 login From 5902c291aca1cadc5746533f6cf5e8b8206ff220 Mon Sep 17 00:00:00 2001 From: Daniil Sapa Date: Wed, 6 Nov 2024 11:08:46 +0200 Subject: [PATCH 06/14] Move the test case for path resolution in extended configs --- .../src/_lib/collect-related-ts-configs.ts | 62 ++++- .../src/insignificant-slice/nuxt.spec.ts | 225 ------------------ 2 files changed, 61 insertions(+), 226 deletions(-) delete mode 100644 packages/steiger-plugin-fsd/src/insignificant-slice/nuxt.spec.ts diff --git a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts index d4263e7..da4edb5 100644 --- a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts +++ b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts @@ -60,7 +60,7 @@ export function collectRelatedTsConfigs(payload: CollectRelatedTsConfigsPayload) } // As tsconfck does not resolve paths in merged configs, -// namely it just takes paths from extended config and puts them to the final merged config we need to do it manually. +// namely it just takes paths from extended configs and puts them to the final merged config, we need to do it manually. // (If some extended config is nested in a folder e.g. ./nuxt/tsconfig.json, // it applies path aliases like '@/': ['../*'] to the project root) function resolveRelativePathsInMergedConfig(configParseResult: CollectRelatedTsConfigsPayload) { @@ -143,4 +143,64 @@ if (import.meta.vitest) { expect(collectRelatedTsConfigs(payload)).toEqual(expectedResult) }) + + test('resolves paths in merged config if the initial config extends some other config', () => { + const payload: CollectRelatedTsConfigsPayload = { + extended: [ + { + tsconfigFile: '/.nuxt/tsconfig.json', + tsconfig: { + compilerOptions: { + paths: { + '~': ['..'], + '~/*': ['../*'], + }, + strict: true, + noUncheckedIndexedAccess: false, + forceConsistentCasingInFileNames: true, + noImplicitOverride: true, + module: 'ESNext', + noEmit: true, + }, + }, + }, + ], + tsconfigFile: '/tsconfig.json', + tsconfig: { + extends: './nuxt/tsconfig.json', + compilerOptions: { + paths: { + '~': ['..'], + '~/*': ['../*'], + }, + strict: true, + noUncheckedIndexedAccess: false, + forceConsistentCasingInFileNames: true, + noImplicitOverride: true, + module: 'ESNext', + noEmit: true, + }, + }, + } + + const expectedResult = [ + { + extends: './nuxt/tsconfig.json', + compilerOptions: { + paths: { + '~': ['/'], + '~/*': ['/*'], + }, + strict: true, + noUncheckedIndexedAccess: false, + forceConsistentCasingInFileNames: true, + noImplicitOverride: true, + module: 'ESNext', + noEmit: true, + }, + }, + ] + + expect(collectRelatedTsConfigs(payload)).toEqual(expectedResult) + }) } diff --git a/packages/steiger-plugin-fsd/src/insignificant-slice/nuxt.spec.ts b/packages/steiger-plugin-fsd/src/insignificant-slice/nuxt.spec.ts deleted file mode 100644 index 4982d67..0000000 --- a/packages/steiger-plugin-fsd/src/insignificant-slice/nuxt.spec.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { expect, it, vi } from 'vitest' - -import { parseIntoFolder } from '@steiger/toolkit' -import insignificantSlice from './index.js' - -vi.mock('tsconfck', async (importOriginal) => { - return { - ...(await importOriginal()), - parse: vi.fn(() => - Promise.resolve({ - extended: [ - { - tsconfigFile: '/tsconfig.json', - tsconfig: { - compilerOptions: { - paths: { - nitropack: ['../node_modules/.pnpm/nitropack@2.9.7_magicast@0.3.5/node_modules/nitropack'], - defu: ['../node_modules/.pnpm/defu@6.1.4/node_modules/defu'], - h3: ['../node_modules/.pnpm/h3@1.13.0/node_modules/h3'], - consola: ['../node_modules/.pnpm/consola@3.2.3/node_modules/consola'], - ofetch: ['../node_modules/.pnpm/ofetch@1.4.1/node_modules/ofetch'], - '@unhead/vue': ['../node_modules/.pnpm/@unhead+vue@1.11.10_vue@3.5.12/node_modules/@unhead/vue'], - '@nuxt/devtools': [ - '../node_modules/.pnpm/@nuxt+devtools@1.6.0_rollup@4.24.0_vite@5.4.9_@types+node@22.7.7_terser@5.36.0__vue@3.5.12/node_modules/@nuxt/devtools', - ], - '@vue/runtime-core': [ - '../node_modules/.pnpm/@vue+runtime-core@3.5.12/node_modules/@vue/runtime-core', - ], - '@vue/compiler-sfc': [ - '../node_modules/.pnpm/@vue+compiler-sfc@3.5.12/node_modules/@vue/compiler-sfc', - ], - 'unplugin-vue-router/client': [ - '../node_modules/.pnpm/unplugin-vue-router@0.10.8_rollup@4.24.0_vue-router@4.4.5_vue@3.5.12__vue@3.5.12/node_modules/unplugin-vue-router/client', - ], - '@nuxt/schema': ['../node_modules/.pnpm/@nuxt+schema@3.13.2_rollup@4.24.0/node_modules/@nuxt/schema'], - nuxt: [ - '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt', - ], - '~': ['..'], - '~/*': ['../*'], - '@': ['..'], - '@/*': ['../*'], - '~~': ['..'], - '~~/*': ['../*'], - '@@': ['..'], - '@@/*': ['../*'], - assets: ['../assets'], - public: ['../public'], - 'public/*': ['../public/*'], - '#app': [ - '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt/dist/app', - ], - '#app/*': [ - '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt/dist/app/*', - ], - 'vue-demi': [ - '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt/dist/app/compat/vue-demi', - ], - '#vue-router': ['../node_modules/.pnpm/vue-router@4.4.5_vue@3.5.12/node_modules/vue-router'], - '#imports': ['./imports'], - '#app-manifest': ['./manifest/meta/dev'], - '#build': ['.'], - '#build/*': ['./*'], - '#components': ['./components'], - }, - esModuleInterop: true, - skipLibCheck: true, - target: 'ESNext', - allowJs: true, - resolveJsonModule: true, - moduleDetection: 'force', - isolatedModules: true, - verbatimModuleSyntax: true, - strict: true, - noUncheckedIndexedAccess: false, - forceConsistentCasingInFileNames: true, - noImplicitOverride: true, - module: 'ESNext', - noEmit: true, - lib: ['ESNext', 'dom', 'dom.iterable', 'webworker'], - jsx: 'preserve', - jsxImportSource: 'vue', - types: [], - moduleResolution: 'Bundler', - useDefineForClassFields: true, - noImplicitThis: true, - allowSyntheticDefaultImports: true, - }, - }, - }, - ], - tsconfigFile: '/tsconfig.json', - tsconfig: { - extends: './nuxt/tsconfig.json', - compilerOptions: { - paths: { - nitropack: ['../node_modules/.pnpm/nitropack@2.9.7_magicast@0.3.5/node_modules/nitropack'], - defu: ['../node_modules/.pnpm/defu@6.1.4/node_modules/defu'], - h3: ['../node_modules/.pnpm/h3@1.13.0/node_modules/h3'], - consola: ['../node_modules/.pnpm/consola@3.2.3/node_modules/consola'], - ofetch: ['../node_modules/.pnpm/ofetch@1.4.1/node_modules/ofetch'], - '@unhead/vue': ['../node_modules/.pnpm/@unhead+vue@1.11.10_vue@3.5.12/node_modules/@unhead/vue'], - '@nuxt/devtools': [ - '../node_modules/.pnpm/@nuxt+devtools@1.6.0_rollup@4.24.0_vite@5.4.9_@types+node@22.7.7_terser@5.36.0__vue@3.5.12/node_modules/@nuxt/devtools', - ], - '@vue/runtime-core': ['../node_modules/.pnpm/@vue+runtime-core@3.5.12/node_modules/@vue/runtime-core'], - '@vue/compiler-sfc': ['../node_modules/.pnpm/@vue+compiler-sfc@3.5.12/node_modules/@vue/compiler-sfc'], - 'unplugin-vue-router/client': [ - '../node_modules/.pnpm/unplugin-vue-router@0.10.8_rollup@4.24.0_vue-router@4.4.5_vue@3.5.12__vue@3.5.12/node_modules/unplugin-vue-router/client', - ], - '@nuxt/schema': ['../node_modules/.pnpm/@nuxt+schema@3.13.2_rollup@4.24.0/node_modules/@nuxt/schema'], - nuxt: [ - '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt', - ], - '~': ['..'], - '~/*': ['../*'], - '@': ['..'], - '@/*': ['../*'], - '~~': ['..'], - '~~/*': ['../*'], - '@@': ['..'], - '@@/*': ['../*'], - assets: ['../assets'], - public: ['../public'], - 'public/*': ['../public/*'], - '#app': [ - '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt/dist/app', - ], - '#app/*': [ - '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt/dist/app/*', - ], - 'vue-demi': [ - '../node_modules/.pnpm/nuxt@3.13.2_@parcel+watcher@2.4.1_@types+node@22.7.7_ioredis@5.4.1_magicast@0.3.5_rollup@4.24_g23ghcmiilocouryy5tlrkcpeu/node_modules/nuxt/dist/app/compat/vue-demi', - ], - '#vue-router': ['../node_modules/.pnpm/vue-router@4.4.5_vue@3.5.12/node_modules/vue-router'], - '#imports': ['./imports'], - '#app-manifest': ['./manifest/meta/dev'], - '#build': ['.'], - '#build/*': ['./*'], - '#components': ['./components'], - }, - esModuleInterop: true, - skipLibCheck: true, - target: 'ESNext', - allowJs: true, - resolveJsonModule: true, - moduleDetection: 'force', - isolatedModules: true, - verbatimModuleSyntax: true, - strict: true, - noUncheckedIndexedAccess: false, - forceConsistentCasingInFileNames: true, - noImplicitOverride: true, - module: 'ESNext', - noEmit: true, - lib: ['ESNext', 'dom', 'dom.iterable', 'webworker'], - jsx: 'preserve', - jsxImportSource: 'vue', - types: [], - moduleResolution: 'Bundler', - useDefineForClassFields: true, - noImplicitThis: true, - allowSyntheticDefaultImports: true, - }, - }, - }), - ), - } -}) - -vi.mock('node:fs', async (importOriginal) => { - const originalFs = await importOriginal() - const { createFsMocks } = await import('@steiger/toolkit') - - return createFsMocks( - { - '/features/session/register/index.ts': 'import {RegisterForm} from "./ui/RegisterForm.vue"', - '/features/session/register/ui/RegisterForm.vue': '', - - '/features/session/logout/index.ts': 'import {LogoutButton} from "./ui/LogoutButton.vue"', - '/features/session/logout/ui/LogoutButton.vue': '', - - '/features/session/login/index.ts': 'import {LoginForm} from "./ui/LoginForm.vue"', - '/features/session/login/ui/LoginForm.vue': '', - - '/pages/home/index.ts': '', - '/pages/home/ui/home.vue': - ' ', - '/pages/category/index.ts': '', - '/pages/category/ui/category.vue': - ' ', - }, - originalFs, - ) -}) - -it('should report no errors on a project with Vue Single-File Components (*.vue files)', async () => { - const root = parseIntoFolder(` - 📂 features - 📂 session - 📂 login - 📂 ui - LoginForm.vue - 📄 index.ts - 📂 logout - 📂 ui - LogoutButton.vue - 📄 index.ts - 📂 register - 📂 ui - RegisterForm.vue - 📄 index.ts - 📂 pages - 📂 home - 📂 ui - 📄 home.vue - 📄 index.ts - 📂 category - 📂 ui - 📄 category.vue - 📄 index.ts - `) - - expect((await insignificantSlice.check(root)).diagnostics).toEqual([]) -}) From 45ed915d5ed822cf07063ed74b2ee051f41aadb1 Mon Sep 17 00:00:00 2001 From: Daniil Sapa Date: Wed, 6 Nov 2024 12:56:55 +0200 Subject: [PATCH 07/14] Fix tests for Windows --- .../src/_lib/collect-related-ts-configs.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts index da4edb5..0fa3d1c 100644 --- a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts +++ b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts @@ -1,5 +1,5 @@ import { TSConfckParseResult } from 'tsconfck' -import { dirname, join } from 'node:path' +import { dirname, posix } from 'node:path' import path from 'node:path' export type CollectRelatedTsConfigsPayload = { @@ -71,7 +71,8 @@ function resolveRelativePathsInMergedConfig(configParseResult: CollectRelatedTsC return parseResult } - const extendAbsolutePath = join(dirname(parseResult.tsconfigFile), parseResult.tsconfig.extends) + // As we work with some king of globs, we need to use posix path separators + const extendAbsolutePath = posix.join(dirname(parseResult.tsconfigFile), parseResult.tsconfig.extends) const extendedConfig = (extended || []).find(({ tsconfigFile }) => tsconfigFile === extendAbsolutePath) return findFirstConfigWithPaths(extendedConfig!) From 81d71733818b6187dad225141fcc2af0c717ce51 Mon Sep 17 00:00:00 2001 From: Daniil Sapa Date: Wed, 6 Nov 2024 13:05:04 +0200 Subject: [PATCH 08/14] Add extended to collectRelatedTsConfigs calls in other rules --- packages/steiger-plugin-fsd/src/forbidden-imports/index.ts | 4 ++-- packages/steiger-plugin-fsd/src/import-locality/index.ts | 4 ++-- .../steiger-plugin-fsd/src/no-public-api-sidestep/index.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/steiger-plugin-fsd/src/forbidden-imports/index.ts b/packages/steiger-plugin-fsd/src/forbidden-imports/index.ts index 137bf46..7ff99ea 100644 --- a/packages/steiger-plugin-fsd/src/forbidden-imports/index.ts +++ b/packages/steiger-plugin-fsd/src/forbidden-imports/index.ts @@ -15,8 +15,8 @@ const forbiddenImports = { name: `${NAMESPACE}/forbidden-imports` as const, async check(root) { const diagnostics: Array = [] - const { tsconfig, referenced } = await parseNearestTsConfig(root.path) - const tsConfigs = collectRelatedTsConfigs({ tsconfig, referenced }) + const parseResult = await parseNearestTsConfig(root.path) + const tsConfigs = collectRelatedTsConfigs(parseResult) const sourceFileIndex = indexSourceFiles(root) for (const sourceFile of Object.values(sourceFileIndex)) { diff --git a/packages/steiger-plugin-fsd/src/import-locality/index.ts b/packages/steiger-plugin-fsd/src/import-locality/index.ts index 0ec4e4a..7dd3db2 100644 --- a/packages/steiger-plugin-fsd/src/import-locality/index.ts +++ b/packages/steiger-plugin-fsd/src/import-locality/index.ts @@ -13,8 +13,8 @@ const importLocality = { name: `${NAMESPACE}/import-locality`, async check(root) { const diagnostics: Array = [] - const { tsconfig, referenced } = await parseNearestTsConfig(root.path) - const tsConfigs = collectRelatedTsConfigs({ tsconfig, referenced }) + const parseResult = await parseNearestTsConfig(root.path) + const tsConfigs = collectRelatedTsConfigs(parseResult) const sourceFileIndex = indexSourceFiles(root) for (const sourceFile of Object.values(sourceFileIndex)) { diff --git a/packages/steiger-plugin-fsd/src/no-public-api-sidestep/index.ts b/packages/steiger-plugin-fsd/src/no-public-api-sidestep/index.ts index 1b15b3a..631b1b0 100644 --- a/packages/steiger-plugin-fsd/src/no-public-api-sidestep/index.ts +++ b/packages/steiger-plugin-fsd/src/no-public-api-sidestep/index.ts @@ -15,8 +15,8 @@ const noPublicApiSidestep = { name: `${NAMESPACE}/no-public-api-sidestep` as const, async check(root) { const diagnostics: Array = [] - const { tsconfig, referenced } = await parseNearestTsConfig(root.path) - const tsConfigs = collectRelatedTsConfigs({ tsconfig, referenced }) + const parseResult = await parseNearestTsConfig(root.path) + const tsConfigs = collectRelatedTsConfigs(parseResult) const sourceFileIndex = indexSourceFiles(root) for (const sourceFile of Object.values(sourceFileIndex)) { From 3b238d65cb0c8d20b225269ae192db6cb322f5bc Mon Sep 17 00:00:00 2001 From: Daniil Sapa Date: Wed, 6 Nov 2024 13:12:45 +0200 Subject: [PATCH 09/14] Button up the solution --- .../src/_lib/collect-related-ts-configs.ts | 75 ++++++++++--------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts index 0fa3d1c..0eeaa8c 100644 --- a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts +++ b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts @@ -64,41 +64,6 @@ export function collectRelatedTsConfigs(payload: CollectRelatedTsConfigsPayload) // (If some extended config is nested in a folder e.g. ./nuxt/tsconfig.json, // it applies path aliases like '@/': ['../*'] to the project root) function resolveRelativePathsInMergedConfig(configParseResult: CollectRelatedTsConfigsPayload) { - function findFirstConfigWithPaths( - parseResult: CollectRelatedTsConfigsPayload, - ): CollectRelatedTsConfigsPayload | null { - if (parseResult.tsconfig.compilerOptions?.paths || !parseResult.tsconfigFile) { - return parseResult - } - - // As we work with some king of globs, we need to use posix path separators - const extendAbsolutePath = posix.join(dirname(parseResult.tsconfigFile), parseResult.tsconfig.extends) - const extendedConfig = (extended || []).find(({ tsconfigFile }) => tsconfigFile === extendAbsolutePath) - - return findFirstConfigWithPaths(extendedConfig!) - } - - function turnPathAliasesIntoAbsolute( - finalConfig: CollectRelatedTsConfigsPayload, - firstConfigWithPaths: CollectRelatedTsConfigsPayload, - ) { - const { tsconfig: mergedConfig } = finalConfig - const absolutePaths: Record> = {} - - if (!firstConfigWithPaths.tsconfigFile) { - return mergedConfig.compilerOptions.paths - } - - for (const entries of Object.entries(mergedConfig.compilerOptions.paths)) { - const [key, paths] = entries as [key: string, paths: Array] - absolutePaths[key] = paths.map((relativePath: string) => - path.resolve(path.dirname(firstConfigWithPaths.tsconfigFile!), relativePath), - ) - } - - return absolutePaths - } - const { tsconfig: mergedConfig, extended } = configParseResult if ( @@ -115,8 +80,8 @@ function resolveRelativePathsInMergedConfig(configParseResult: CollectRelatedTsC } // Find the first config with paths in the "extends" chain as it overrides the others - const firstConfigWithPaths = findFirstConfigWithPaths(configParseResult) - const absolutePaths = turnPathAliasesIntoAbsolute(configParseResult, firstConfigWithPaths!) + const firstConfigWithPaths = findFirstConfigWithPaths(configParseResult, extended || []) + const absolutePaths = makeRelativePathAliasesAbsolute(configParseResult, firstConfigWithPaths!) return { ...mergedConfig, @@ -127,6 +92,42 @@ function resolveRelativePathsInMergedConfig(configParseResult: CollectRelatedTsC } } +function findFirstConfigWithPaths( + parseResult: CollectRelatedTsConfigsPayload, + extended: TSConfckParseResult[], +): CollectRelatedTsConfigsPayload | null { + if (parseResult.tsconfig.compilerOptions?.paths || !parseResult.tsconfigFile) { + return parseResult + } + + // As we work with some king of globs, we need to use posix path separators + const extendAbsolutePath = posix.join(dirname(parseResult.tsconfigFile), parseResult.tsconfig.extends) + const extendedConfig = extended.find(({ tsconfigFile }) => tsconfigFile === extendAbsolutePath) + + return findFirstConfigWithPaths(extendedConfig!, extended) +} + +function makeRelativePathAliasesAbsolute( + finalConfig: CollectRelatedTsConfigsPayload, + firstConfigWithPaths: CollectRelatedTsConfigsPayload, +) { + const { tsconfig: mergedConfig } = finalConfig + const absolutePaths: Record> = {} + + if (!firstConfigWithPaths.tsconfigFile) { + return mergedConfig.compilerOptions.paths + } + + for (const entries of Object.entries(mergedConfig.compilerOptions.paths)) { + const [key, paths] = entries as [key: string, paths: Array] + absolutePaths[key] = paths.map((relativePath: string) => + path.resolve(path.dirname(firstConfigWithPaths.tsconfigFile!), relativePath), + ) + } + + return absolutePaths +} + if (import.meta.vitest) { const { test, expect } = import.meta.vitest From e945f291865c5ff037df4df98a86fd7a1ba58130 Mon Sep 17 00:00:00 2001 From: Daniil Sapa Date: Wed, 6 Nov 2024 13:21:33 +0200 Subject: [PATCH 10/14] Fix test cases for Windows --- .../steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts index 0eeaa8c..101fa47 100644 --- a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts +++ b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts @@ -1,6 +1,5 @@ import { TSConfckParseResult } from 'tsconfck' import { dirname, posix } from 'node:path' -import path from 'node:path' export type CollectRelatedTsConfigsPayload = { tsconfig: TSConfckParseResult['tsconfig'] @@ -121,7 +120,7 @@ function makeRelativePathAliasesAbsolute( for (const entries of Object.entries(mergedConfig.compilerOptions.paths)) { const [key, paths] = entries as [key: string, paths: Array] absolutePaths[key] = paths.map((relativePath: string) => - path.resolve(path.dirname(firstConfigWithPaths.tsconfigFile!), relativePath), + posix.resolve(dirname(firstConfigWithPaths.tsconfigFile!), relativePath), ) } From cf422ec5da19599934f9a9a33afadf14ba05256b Mon Sep 17 00:00:00 2001 From: Daniil Sapa Date: Mon, 11 Nov 2024 13:39:03 +0200 Subject: [PATCH 11/14] Simplify path resolution logic --- .../src/_lib/collect-related-ts-configs.ts | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts index 101fa47..edfe97c 100644 --- a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts +++ b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts @@ -79,7 +79,7 @@ function resolveRelativePathsInMergedConfig(configParseResult: CollectRelatedTsC } // Find the first config with paths in the "extends" chain as it overrides the others - const firstConfigWithPaths = findFirstConfigWithPaths(configParseResult, extended || []) + const firstConfigWithPaths = findFirstConfigWithPaths(extended || []) || configParseResult const absolutePaths = makeRelativePathAliasesAbsolute(configParseResult, firstConfigWithPaths!) return { @@ -91,19 +91,14 @@ function resolveRelativePathsInMergedConfig(configParseResult: CollectRelatedTsC } } -function findFirstConfigWithPaths( - parseResult: CollectRelatedTsConfigsPayload, - extended: TSConfckParseResult[], -): CollectRelatedTsConfigsPayload | null { - if (parseResult.tsconfig.compilerOptions?.paths || !parseResult.tsconfigFile) { - return parseResult - } - - // As we work with some king of globs, we need to use posix path separators - const extendAbsolutePath = posix.join(dirname(parseResult.tsconfigFile), parseResult.tsconfig.extends) - const extendedConfig = extended.find(({ tsconfigFile }) => tsconfigFile === extendAbsolutePath) +function findFirstConfigWithPaths(extended: TSConfckParseResult[]): TSConfckParseResult | undefined { + for (const parseResult of extended) { + const { tsconfig } = parseResult - return findFirstConfigWithPaths(extendedConfig!, extended) + if (tsconfig.compilerOptions?.paths) { + return parseResult + } + } } function makeRelativePathAliasesAbsolute( @@ -168,7 +163,7 @@ if (import.meta.vitest) { ], tsconfigFile: '/tsconfig.json', tsconfig: { - extends: './nuxt/tsconfig.json', + extends: './.nuxt/tsconfig.json', compilerOptions: { paths: { '~': ['..'], @@ -186,7 +181,7 @@ if (import.meta.vitest) { const expectedResult = [ { - extends: './nuxt/tsconfig.json', + extends: './.nuxt/tsconfig.json', compilerOptions: { paths: { '~': ['/'], From 37baccb7ee8ce10d7a744923997bed0dfa024a67 Mon Sep 17 00:00:00 2001 From: Daniil Sapa Date: Mon, 11 Nov 2024 18:10:13 +0200 Subject: [PATCH 12/14] Make paths absolute for paths in tsconfig to make Steiger independent of where it runs --- .../src/_lib/collect-related-ts-configs.ts | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts index edfe97c..b2176df 100644 --- a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts +++ b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts @@ -60,6 +60,8 @@ export function collectRelatedTsConfigs(payload: CollectRelatedTsConfigsPayload) // As tsconfck does not resolve paths in merged configs, // namely it just takes paths from extended configs and puts them to the final merged config, we need to do it manually. +// Another reason to transform paths is that otherwise they are resolved relative +// to the folder where Steiger is launched (__dirname), so we need to make them absolute. // (If some extended config is nested in a folder e.g. ./nuxt/tsconfig.json, // it applies path aliases like '@/': ['../*'] to the project root) function resolveRelativePathsInMergedConfig(configParseResult: CollectRelatedTsConfigsPayload) { @@ -68,8 +70,6 @@ function resolveRelativePathsInMergedConfig(configParseResult: CollectRelatedTsC if ( // if there's no config it needs to be handled somewhere else !mergedConfig || - // if the merged config does not have "extends" there are no paths to resolve - !mergedConfig.extends || // if the merged config does not have compilerOptions, there is nothing to resolve !mergedConfig.compilerOptions || // if the merged config does not have paths in compilerOptions there's nothing to resolve @@ -143,6 +143,12 @@ if (import.meta.vitest) { test('resolves paths in merged config if the initial config extends some other config', () => { const payload: CollectRelatedTsConfigsPayload = { extended: [ + { + tsconfigFile: '/tsconfig.json', + tsconfig: { + extends: './.nuxt/tsconfig.json', + }, + }, { tsconfigFile: '/.nuxt/tsconfig.json', tsconfig: { @@ -199,4 +205,43 @@ if (import.meta.vitest) { expect(collectRelatedTsConfigs(payload)).toEqual(expectedResult) }) + + test('resolves p', () => { + const payload: CollectRelatedTsConfigsPayload = { + tsconfigFile: '/user/projects/project-0/tsconfig.json', + tsconfig: { + compilerOptions: { + paths: { + '~': ['./src'], + '~/*': ['./src/*'], + }, + strict: true, + noUncheckedIndexedAccess: false, + forceConsistentCasingInFileNames: true, + noImplicitOverride: true, + module: 'ESNext', + noEmit: true, + }, + }, + } + + const expectedResult = [ + { + compilerOptions: { + paths: { + '~': ['/user/projects/project-0/src'], + '~/*': ['/user/projects/project-0/src/*'], + }, + strict: true, + noUncheckedIndexedAccess: false, + forceConsistentCasingInFileNames: true, + noImplicitOverride: true, + module: 'ESNext', + noEmit: true, + }, + }, + ] + + expect(collectRelatedTsConfigs(payload)).toEqual(expectedResult) + }) } From 0fa74646a83e30671d926f5aa42f90613fe25ef6 Mon Sep 17 00:00:00 2001 From: Lev Chelyadinov Date: Mon, 11 Nov 2024 20:54:51 +0100 Subject: [PATCH 13/14] Finish the description of a test --- .../steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts index b2176df..fd05c68 100644 --- a/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts +++ b/packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts @@ -206,7 +206,7 @@ if (import.meta.vitest) { expect(collectRelatedTsConfigs(payload)).toEqual(expectedResult) }) - test('resolves p', () => { + test('resolves paths independently from the current directory', () => { const payload: CollectRelatedTsConfigsPayload = { tsconfigFile: '/user/projects/project-0/tsconfig.json', tsconfig: { From 17b4ff8a3c35c9c75e1f2859c3a100d187bce9a5 Mon Sep 17 00:00:00 2001 From: Lev Chelyadinov Date: Mon, 11 Nov 2024 21:14:47 +0100 Subject: [PATCH 14/14] Fix finding the tsconfig in the root folder --- packages/steiger-plugin-fsd/src/forbidden-imports/index.ts | 2 +- packages/steiger-plugin-fsd/src/import-locality/index.ts | 2 +- packages/steiger-plugin-fsd/src/insignificant-slice/index.ts | 2 +- packages/steiger-plugin-fsd/src/no-public-api-sidestep/index.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/steiger-plugin-fsd/src/forbidden-imports/index.ts b/packages/steiger-plugin-fsd/src/forbidden-imports/index.ts index 7ff99ea..929d55a 100644 --- a/packages/steiger-plugin-fsd/src/forbidden-imports/index.ts +++ b/packages/steiger-plugin-fsd/src/forbidden-imports/index.ts @@ -15,7 +15,7 @@ const forbiddenImports = { name: `${NAMESPACE}/forbidden-imports` as const, async check(root) { const diagnostics: Array = [] - const parseResult = await parseNearestTsConfig(root.path) + const parseResult = await parseNearestTsConfig(root.children[0]?.path ?? root.path) const tsConfigs = collectRelatedTsConfigs(parseResult) const sourceFileIndex = indexSourceFiles(root) diff --git a/packages/steiger-plugin-fsd/src/import-locality/index.ts b/packages/steiger-plugin-fsd/src/import-locality/index.ts index 7dd3db2..e2cff32 100644 --- a/packages/steiger-plugin-fsd/src/import-locality/index.ts +++ b/packages/steiger-plugin-fsd/src/import-locality/index.ts @@ -13,7 +13,7 @@ const importLocality = { name: `${NAMESPACE}/import-locality`, async check(root) { const diagnostics: Array = [] - const parseResult = await parseNearestTsConfig(root.path) + const parseResult = await parseNearestTsConfig(root.children[0]?.path ?? root.path) const tsConfigs = collectRelatedTsConfigs(parseResult) const sourceFileIndex = indexSourceFiles(root) diff --git a/packages/steiger-plugin-fsd/src/insignificant-slice/index.ts b/packages/steiger-plugin-fsd/src/insignificant-slice/index.ts index 393fede..88c013f 100644 --- a/packages/steiger-plugin-fsd/src/insignificant-slice/index.ts +++ b/packages/steiger-plugin-fsd/src/insignificant-slice/index.ts @@ -53,7 +53,7 @@ export default insignificantSlice async function traceSliceReferences(root: Folder) { const sourceFileIndex = indexSourceFiles(root) - const parseResult = await parseNearestTsConfig(root.path) + const parseResult = await parseNearestTsConfig(root.children[0]?.path ?? root.path) const tsConfigs = collectRelatedTsConfigs(parseResult) const references = new Map>() diff --git a/packages/steiger-plugin-fsd/src/no-public-api-sidestep/index.ts b/packages/steiger-plugin-fsd/src/no-public-api-sidestep/index.ts index 631b1b0..0afd3ab 100644 --- a/packages/steiger-plugin-fsd/src/no-public-api-sidestep/index.ts +++ b/packages/steiger-plugin-fsd/src/no-public-api-sidestep/index.ts @@ -15,7 +15,7 @@ const noPublicApiSidestep = { name: `${NAMESPACE}/no-public-api-sidestep` as const, async check(root) { const diagnostics: Array = [] - const parseResult = await parseNearestTsConfig(root.path) + const parseResult = await parseNearestTsConfig(root.children[0]?.path ?? root.path) const tsConfigs = collectRelatedTsConfigs(parseResult) const sourceFileIndex = indexSourceFiles(root)