Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

References in Nuxt setup #117

Merged
merged 15 commits into from
Nov 11, 2024
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
5 changes: 5 additions & 0 deletions .changeset/dull-rice-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@feature-sliced/steiger-plugin': patch
---

Add a Vue test case for insignificant-slice
176 changes: 174 additions & 2 deletions packages/steiger-plugin-fsd/src/_lib/collect-related-ts-configs.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { TSConfckParseResult } from 'tsconfck'
import { dirname, posix } from 'node:path'

export type CollectRelatedTsConfigsPayload = {
tsconfig: TSConfckParseResult['tsconfig']
tsconfigFile?: TSConfckParseResult['tsconfigFile']
referenced?: CollectRelatedTsConfigsPayload[]
extended?: TSConfckParseResult[]
}

/**
Expand Down Expand Up @@ -46,15 +49,79 @@ export type CollectRelatedTsConfigsPayload = {
export function collectRelatedTsConfigs(payload: CollectRelatedTsConfigsPayload) {
const configs: Array<CollectRelatedTsConfigsPayload['tsconfig']> = []

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)

return configs
}

// 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) {
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 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(extended || []) || configParseResult
const absolutePaths = makeRelativePathAliasesAbsolute(configParseResult, firstConfigWithPaths!)

return {
...mergedConfig,
compilerOptions: {
...mergedConfig.compilerOptions,
paths: absolutePaths,
},
}
}

function findFirstConfigWithPaths(extended: TSConfckParseResult[]): TSConfckParseResult | undefined {
for (const parseResult of extended) {
const { tsconfig } = parseResult

if (tsconfig.compilerOptions?.paths) {
return parseResult
}
}
}

function makeRelativePathAliasesAbsolute(
finalConfig: CollectRelatedTsConfigsPayload,
firstConfigWithPaths: CollectRelatedTsConfigsPayload,
) {
const { tsconfig: mergedConfig } = finalConfig
const absolutePaths: Record<string, Array<string>> = {}

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<string>]
absolutePaths[key] = paths.map((relativePath: string) =>
posix.resolve(dirname(firstConfigWithPaths.tsconfigFile!), relativePath),
)
}

return absolutePaths
}

if (import.meta.vitest) {
const { test, expect } = import.meta.vitest

Expand All @@ -72,4 +139,109 @@ 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: '/tsconfig.json',
tsconfig: {
extends: './.nuxt/tsconfig.json',
},
},
{
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)
})

test('resolves paths independently from the current directory', () => {
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)
})
}
4 changes: 2 additions & 2 deletions packages/steiger-plugin-fsd/src/forbidden-imports/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ const forbiddenImports = {
name: `${NAMESPACE}/forbidden-imports` as const,
async check(root) {
const diagnostics: Array<PartialDiagnostic> = []
const { tsconfig, referenced } = await parseNearestTsConfig(root.path)
const tsConfigs = collectRelatedTsConfigs({ tsconfig, referenced })
const parseResult = await parseNearestTsConfig(root.children[0]?.path ?? root.path)
const tsConfigs = collectRelatedTsConfigs(parseResult)
const sourceFileIndex = indexSourceFiles(root)

for (const sourceFile of Object.values(sourceFileIndex)) {
Expand Down
4 changes: 2 additions & 2 deletions packages/steiger-plugin-fsd/src/import-locality/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ const importLocality = {
name: `${NAMESPACE}/import-locality`,
async check(root) {
const diagnostics: Array<PartialDiagnostic> = []
const { tsconfig, referenced } = await parseNearestTsConfig(root.path)
const tsConfigs = collectRelatedTsConfigs({ tsconfig, referenced })
const parseResult = await parseNearestTsConfig(root.children[0]?.path ?? root.path)
const tsConfigs = collectRelatedTsConfigs(parseResult)
const sourceFileIndex = indexSourceFiles(root)

for (const sourceFile of Object.values(sourceFileIndex)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,18 @@ 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': '',

'/features/comments/ui/CommentCard.tsx': '',
'/features/comments/index.ts': '',

'/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':
Expand All @@ -37,6 +41,8 @@ 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/category/index.ts': '',
},
originalFs,
)
Expand Down
4 changes: 2 additions & 2 deletions packages/steiger-plugin-fsd/src/insignificant-slice/index.ts
illright marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -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.children[0]?.path ?? root.path)
const tsConfigs = collectRelatedTsConfigs(parseResult)
const references = new Map<string, Set<string>>()

for (const sourceFile of Object.values(sourceFileIndex)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ const noPublicApiSidestep = {
name: `${NAMESPACE}/no-public-api-sidestep` as const,
async check(root) {
const diagnostics: Array<PartialDiagnostic> = []
const { tsconfig, referenced } = await parseNearestTsConfig(root.path)
const tsConfigs = collectRelatedTsConfigs({ tsconfig, referenced })
const parseResult = await parseNearestTsConfig(root.children[0]?.path ?? root.path)
const tsConfigs = collectRelatedTsConfigs(parseResult)
const sourceFileIndex = indexSourceFiles(root)

for (const sourceFile of Object.values(sourceFileIndex)) {
Expand Down