Skip to content

Commit

Permalink
feat: remove a warning, add more typecheck tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed Aug 13, 2024
1 parent bdc5b6b commit 6173593
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 68 deletions.
3 changes: 0 additions & 3 deletions packages/vitest/src/node/cli/cli-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -639,9 +639,6 @@ export const cliOptionsConfig: VitestCLIOptions = {
ignoreSourceErrors: {
description: 'Ignore type errors from source files',
},
ignoreCollectWarnings: {
description: 'Do not show warnings from the collector during typechecking',
},
tsconfig: {
description: 'Path to a custom tsconfig file',
argument: '<path>',
Expand Down
4 changes: 0 additions & 4 deletions packages/vitest/src/node/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -862,10 +862,6 @@ export interface TypecheckConfig {
* Do not fail, if Vitest found errors outside the test files.
*/
ignoreSourceErrors?: boolean
/**
* Do not show warnings from the collector during typechecking.
*/
ignoreCollectWarnings?: boolean
/**
* Path to tsconfig, relative to the project root.
*/
Expand Down
88 changes: 28 additions & 60 deletions packages/vitest/src/typecheck/collect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { readFileSync } from 'node:fs'
import { relative } from 'pathe'
import { parseAstAsync } from 'vite'
import { ancestor as walkAst } from 'acorn-walk'
Expand All @@ -11,7 +10,6 @@ import {
} from '@vitest/runner/utils'
import type { File, Suite, Test } from '@vitest/runner'
import type { WorkspaceProject } from '../node/workspace'
import { generateCodeFrame } from '../node/error'

interface ParsedFile extends File {
start: number
Expand Down Expand Up @@ -53,7 +51,6 @@ export async function collectTests(
if (!request) {
return null
}
const vitest = ctx.ctx
const ast = await parseAstAsync(request.code)
const testFilepath = relative(ctx.config.root, filepath)
const projectName = ctx.getName()
Expand Down Expand Up @@ -97,38 +94,6 @@ export async function collectTests(
return null
}

const nodeFrames = new WeakSet<Node>()

function getCodeFrame(node: any, start?: number) {
if (nodeFrames.has(node)) {
return '' // don't duplicate the node
}
const higlight = vitest.logger.highlight(filepath, readFileSync(filepath, 'utf8'))
const codeframe = generateCodeFrame(
higlight,
4,
start ?? (node.start + 1),
)
nodeFrames.add(node)
return codeframe
}

function warn(message: string, node: any, start?: number) {
if (ctx.config.typecheck.ignoreCollectWarnings) {
return
}
const codeframe = getCodeFrame(
node,
start ?? (node.start + 1),
)
if (codeframe) {
message += `:\n${codeframe}`
}
vitest.logger.warn(
`${message}\nIf you want so suppress these messages, set \`typecheck.ignoreCollectWarnings: true\` in your config.`,
)
}

walkAst(ast as any, {
CallExpression(node) {
const { callee } = node as any
Expand All @@ -140,9 +105,9 @@ export async function collectTests(
return
}
const property = callee?.property?.name
let mode = !property || property === name ? 'run' : property
if (mode === 'each') {
warn(`Type checker doesn't support ${name}.each`, node)
const mode = !property || property === name ? 'run' : property
// the test node for skipIf and runIf will be the next CallExpression
if (mode === 'each' || mode === 'skipIf' || mode === 'runIf') {
return
}

Expand All @@ -162,27 +127,8 @@ export async function collectTests(
const {
arguments: [messageNode],
} = node
let message: string = 'unknown'

if (messageNode.type === 'Literal') {
message = String(messageNode.value)
}
else if (messageNode.type === 'Identifier') {
message = messageNode.name
}
else if (messageNode.type === 'TemplateLiteral') {
message = mergeTemplateLiteral(messageNode as any)
}
else {
message = 'unknown'
warn(`Type checker cannot statically analyze the message of ${name}, fallback to "unknown"`, node, start)
}
const message = getNodeAsString(messageNode, request.code)

// cannot statically analyze, so we always skip it
if (mode === 'skipIf' || mode === 'runIf') {
mode = 'skip'
warn(`Type checker cannot statically analyze the mode of ${name}.${mode}, fallback to "skip"`, node, start)
}
definitions.push({
start,
end,
Expand Down Expand Up @@ -264,14 +210,36 @@ export async function collectTests(
}
}

function mergeTemplateLiteral(node: any): string {
function getNodeAsString(node: any, code: string): string {
if (node.type === 'Literal') {
return String(node.value)
}
else if (node.type === 'Identifier') {
return node.name
}
else if (node.type === 'TemplateLiteral') {
return mergeTemplateLiteral(node, code)
}
else {
return code.slice(node.start, node.end)
}
}

function mergeTemplateLiteral(node: any, code: string): string {
let result = ''
let expressionsIndex = 0

for (let quasisIndex = 0; quasisIndex < node.quasis.length; quasisIndex++) {
result += node.quasis[quasisIndex].value.raw
if (expressionsIndex in node.expressions) {
result += `{${node.expressions[expressionsIndex]}}`
const expression = node.expressions[expressionsIndex]
const string = expression.type === 'Literal' ? expression.raw : getNodeAsString(expression, code)
if (expression.type === 'TemplateLiteral') {
result += `\${\`${string}\`}`
}
else {
result += `\${${string}}`
}
expressionsIndex++
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/typecheck/typechecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export class Typechecker {
...definitions.sort((a, b) => b.start - a.start),
]
// has no map for ".js" files that use // @ts-check
const traceMap = map && new TraceMap(map as unknown as RawSourceMap)
const traceMap = (map && new TraceMap(map as unknown as RawSourceMap))
const indexMap = createIndexMap(parsed)
const markState = (task: Task, state: TaskState) => {
task.result = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { expectTypeOf, test } from 'vitest'

test.each(['some-value'])('%s', () => {
expectTypeOf(1).toEqualTypeOf(2)
})

test.skipIf(false)('dynamic skip', () => {
expectTypeOf(1).toEqualTypeOf(2)
})

test(`template string`, () => {
expectTypeOf(1).toEqualTypeOf(2)
})

test(`template ${'some value'} string`, () => {
expectTypeOf(1).toEqualTypeOf(2)
})

test(`template ${`literal`} string`, () => {
expectTypeOf(1).toEqualTypeOf(2)
})

const name = 'some value'
test(name, () => {
expectTypeOf(1).toEqualTypeOf(2)
})

test((() => 'some name')(), () => {
expectTypeOf(1).toEqualTypeOf(2)
})
12 changes: 12 additions & 0 deletions test/typescript/fixtures/dynamic-title/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"noEmit": true,
"target": "es2020",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"verbatimModuleSyntax": true
},
"include": ["test"],
"exclude": ["node_modules"]
}
10 changes: 10 additions & 0 deletions test/typescript/fixtures/dynamic-title/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
typecheck: {
enabled: true,
tsconfig: './tsconfig.json',
},
},
})
19 changes: 19 additions & 0 deletions test/typescript/test/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,22 @@ describe('ignoreSourceErrors', () => {
expect(vitest.stderr).not.toContain('TypeCheckError: Cannot find name \'thisIsSourceError\'')
})
})

describe('when the title is dynamic', () => {
it('correctly works', async () => {
const vitest = await runVitest({
root: resolve(__dirname, '../fixtures/dynamic-title'),
})

expect(vitest.stdout).toContain('✓ %s')
expect(vitest.stdout).toContain('✓ dynamic skip')
expect(vitest.stdout).not.toContain('✓ false') // .skipIf is not reported as a separate test
expect(vitest.stdout).toContain('✓ template string')
// eslint-disable-next-line no-template-curly-in-string
expect(vitest.stdout).toContain('✓ template ${"some value"} string')
// eslint-disable-next-line no-template-curly-in-string
expect(vitest.stdout).toContain('✓ template ${`literal`} string')
expect(vitest.stdout).toContain('✓ name')
expect(vitest.stdout).toContain('✓ (() => "some name")()')
})
})
4 changes: 4 additions & 0 deletions test/typescript/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"include": [
"./**/*.ts",
"./**/*.js"
],
"exclude": [
"**/dist/**",
"**/fixtures/**"
Expand Down

0 comments on commit 6173593

Please sign in to comment.