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

feat(validate): add lint warnings #16458

Merged
merged 5 commits into from
Nov 28, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/cli/src/Doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ ${chalk.bold('Examples')}

const schema = await readFile(schemaPath, 'utf-8')
const localDmmf = await getDMMF({ datamodel: schema })
const config = await getConfig({ datamodel: schema })
const config = await getConfig({ datamodel: schema, ignoreEnvVarErrors: false })
jkomyno marked this conversation as resolved.
Show resolved Hide resolved

console.error(`👩‍⚕️🏥 Prisma Doctor checking the database...`)

Expand Down
6 changes: 2 additions & 4 deletions packages/cli/src/Format.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { Command } from '@prisma/internals'
import { arg, format, formatms, formatSchema, getDMMF, HelpError } from '@prisma/internals'
import { arg, Command, format, formatms, formatSchema, getDMMF, HelpError } from '@prisma/internals'
import { getSchemaPathAndPrint } from '@prisma/migrate'
import chalk from 'chalk'
import fs from 'fs'
import os from 'os'

/**
* $ prisma format
Expand Down Expand Up @@ -63,7 +61,7 @@ Or specify a Prisma schema path
})
} catch (e) {
console.error('') // empty line for better readability
throw new Error(`${e.message}`)
throw e
}

fs.writeFileSync(schemaPath, output)
Expand Down
42 changes: 37 additions & 5 deletions packages/cli/src/Validate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import type { Command } from '@prisma/internals'
import { arg, format, getConfig, getDMMF, HelpError, loadEnvFile } from '@prisma/internals'
import {
arg,
format,
getConfig,
getDMMF,
getLintWarningsAsText,
handleLintPanic,
HelpError,
lintSchema,
loadEnvFile,
} from '@prisma/internals'
import { getSchemaPathAndPrint } from '@prisma/migrate'
import chalk from 'chalk'
import fs from 'fs'
Expand Down Expand Up @@ -56,13 +66,35 @@ ${chalk.bold('Examples')}

const schema = fs.readFileSync(schemaPath, 'utf-8')

// TODO is the order of getDMMF / getConfig important
await getDMMF({
datamodel: schema,
})
const { lintDiagnostics } = handleLintPanic(
() => {
// the only possible error here is a Rust panic
const lintDiagnostics = lintSchema({ schema })
return { lintDiagnostics }
},
{ schema },
)

const lintWarnings = getLintWarningsAsText(lintDiagnostics)
if (lintWarnings) {
// Output warnings to stderr
console.warn(lintWarnings)
}

try {
// Validate whether the formatted output is a valid schema
await getDMMF({
datamodel: schema,
})
} catch (e) {
console.error('') // empty line for better readability
throw e
}

// We could have a CLI flag to ignore env var validation
await getConfig({
datamodel: schema,
ignoreEnvVarErrors: false,
})

return `The schema at ${chalk.underline(schemaPath)} is valid 🚀`
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/Version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export class Version implements Command {
const datamodel = await getSchema()
const config = await getConfig({
datamodel,
ignoreEnvVarErrors: false,
})
const generator = config.generators.find((g) => g.previewFeatures.length > 0)
if (generator) {
Expand Down
57 changes: 55 additions & 2 deletions packages/cli/src/__tests__/artificial-panic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ describeIf(process.env.PRISMA_CLI_QUERY_ENGINE_TYPE == 'library')('artificial-pa
process.env = { ...OLD_ENV }
})

it('query-engine get-dmmf library', async () => {
it('query-engine get-dmmf library - validate', async () => {
ctx.fixture('artificial-panic')
expect.assertions(5)
process.env.FORCE_PANIC_QUERY_ENGINE_GET_DMMF = '1'
Expand Down Expand Up @@ -161,6 +161,38 @@ describeIf(process.env.PRISMA_CLI_QUERY_ENGINE_TYPE == 'library')('artificial-pa
})
}
})

it('query-engine get-dmmf library - format', async () => {
ctx.fixture('artificial-panic')
expect.assertions(5)
process.env.FORCE_PANIC_QUERY_ENGINE_GET_DMMF = '1'

const command = new Format()
try {
await command.parse([])
} catch (e) {
expect(e).toMatchInlineSnapshot(`FORCE_PANIC_QUERY_ENGINE_GET_DMMF`)
expect(isRustPanic(e)).toBe(true)
expect(e.rustStack).toBeTruthy()
expect(e.schema).toMatchInlineSnapshot(`
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

`)
expect(e).toMatchObject({
schemaPath: undefined,
})
}
})
})

describeIf(process.env.PRISMA_CLI_QUERY_ENGINE_TYPE == 'binary')('artificial-panic binary', () => {
Expand All @@ -170,7 +202,7 @@ describeIf(process.env.PRISMA_CLI_QUERY_ENGINE_TYPE == 'binary')('artificial-pan
process.env = { ...OLD_ENV }
})

it('query-engine get-dmmf binary', async () => {
it('query-engine get-dmmf binary - validate', async () => {
ctx.fixture('artificial-panic')
expect.assertions(5)
process.env.FORCE_PANIC_QUERY_ENGINE_GET_DMMF = '1'
Expand All @@ -190,4 +222,25 @@ describeIf(process.env.PRISMA_CLI_QUERY_ENGINE_TYPE == 'binary')('artificial-pan
})
}
})

it('query-engine get-dmmf binary - format', async () => {
ctx.fixture('artificial-panic')
expect.assertions(5)
process.env.FORCE_PANIC_QUERY_ENGINE_GET_DMMF = '1'

const command = new Format()
try {
await command.parse([])
} catch (e) {
expect(e).toMatchInlineSnapshot(
`Command failed with exit code 101: prisma-engines-path FORCE_PANIC_QUERY_ENGINE_GET_DMMF`,
)
expect(isRustPanic(e)).toBe(true)
expect(e.rustStack).toBeTruthy()
expect(e.schemaPath).toBeTruthy()
expect(e).toMatchObject({
schema: undefined,
})
}
})
})
32 changes: 30 additions & 2 deletions packages/cli/src/__tests__/commands/Format.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { jestContext } from '@prisma/internals'
import { jestConsoleContext, jestContext } from '@prisma/internals'
import fs from 'fs-jetpack'

import { Format } from '../../Format'

const ctx = jestContext.new().assemble()
const ctx = jestContext.new().add(jestConsoleContext()).assemble()

it('format should add a trailing EOL', async () => {
ctx.fixture('example-project/prisma')
Expand All @@ -21,3 +21,31 @@ it('format should throw if schema is broken', async () => {
ctx.fixture('example-project/prisma')
await expect(Format.new().parse(['--schema=broken.prisma'])).rejects.toThrowError()
})

it('format should succeed and show a warning on stderr (preview feature deprecated)', async () => {
ctx.fixture('lint-warnings')
await expect(Format.new().parse(['--schema=preview-feature-deprecated.prisma'])).resolves.toBeTruthy()

// stderr
expect(ctx.mocked['console.warn'].mock.calls.join('\n')).toMatchInlineSnapshot(`

Prisma schema warning:
- Preview feature "nativeTypes" is deprecated. The functionality can be used without specifying it as a preview feature.
`)
expect(ctx.mocked['console.error'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
})

it('format should throw with an error and show a warning on stderr (preview feature deprecated)', async () => {
ctx.fixture('lint-warnings')
await expect(Format.new().parse(['--schema=preview-feature-deprecated-and-error.prisma'])).rejects.toThrowError(
'P1012',
)

// stderr
expect(ctx.mocked['console.warn'].mock.calls.join('\n')).toMatchInlineSnapshot(`

Prisma schema warning:
- Preview feature "nativeTypes" is deprecated. The functionality can be used without specifying it as a preview feature.
`)
expect(ctx.mocked['console.error'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
})
32 changes: 30 additions & 2 deletions packages/cli/src/__tests__/commands/Validate.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { jestContext, serializeQueryEngineName } from '@prisma/internals'
import { jestConsoleContext, jestContext, serializeQueryEngineName } from '@prisma/internals'

import { Validate } from '../../Validate'

const ctx = jestContext.new().assemble()
const ctx = jestContext.new().add(jestConsoleContext()).assemble()

it('validate should succeed if schema is valid', async () => {
ctx.fixture('example-project/prisma')
Expand All @@ -21,6 +21,34 @@ it('validate should throw if env var is not set', async () => {
)
})

it('validate should succeed and show a warning on stderr (preview feature deprecated)', async () => {
ctx.fixture('lint-warnings')
await expect(Validate.new().parse(['--schema=preview-feature-deprecated.prisma'])).resolves.toBeTruthy()

// stderr
expect(ctx.mocked['console.warn'].mock.calls.join('\n')).toMatchInlineSnapshot(`

Prisma schema warning:
- Preview feature "nativeTypes" is deprecated. The functionality can be used without specifying it as a preview feature.
`)
expect(ctx.mocked['console.error'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
})

it('validate should throw with an error and show a warning on stderr (preview feature deprecated)', async () => {
ctx.fixture('lint-warnings')
await expect(Validate.new().parse(['--schema=preview-feature-deprecated-and-error.prisma'])).rejects.toThrowError(
'P1012',
)

// stderr
expect(ctx.mocked['console.warn'].mock.calls.join('\n')).toMatchInlineSnapshot(`

Prisma schema warning:
- Preview feature "nativeTypes" is deprecated. The functionality can be used without specifying it as a preview feature.
`)
expect(ctx.mocked['console.error'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
})

describe('referential actions', () => {
beforeEach(() => {
ctx.fixture('referential-actions/no-action/relationMode-prisma')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
generator client {
provider = "prisma-client-js"
previewFeatures = ["nativeTypes", "does-not-exists"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
generator client {
provider = "prisma-client-js"
previewFeatures = ["nativeTypes"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import { MemoryTestDir } from './MemoryTestDir'
export async function generateMemoryTestClient(testDir: MemoryTestDir) {
const schema = await fs.readFile(testDir.schemaFilePath, 'utf8')
const dmmf = await getDMMF({ datamodel: schema, datamodelPath: testDir.schemaFilePath })
const config = await getConfig({ datamodel: schema, datamodelPath: testDir.schemaFilePath })
const config = await getConfig({
datamodel: schema,
datamodelPath: testDir.schemaFilePath,
ignoreEnvVarErrors: false,
})
const generator = config.generators.find((g) => parseEnvValue(g.provider) === 'prisma-client-js')

await generateClient({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ describe('getConfig', () => {

try {
// @ts-expect-error
await getConfig({ datamodel: true })
await getConfig({ datamodel: true, ignoreEnvVarErrors: false })
} catch (e) {
const error = e as Error
expect(isRustPanic(error)).toBe(true)
Expand All @@ -16,6 +16,7 @@ describe('getConfig', () => {

test('empty config', async () => {
const config = await getConfig({
ignoreEnvVarErrors: false,
datamodel: `
datasource db {
provider = "sqlite"
Expand All @@ -37,6 +38,7 @@ describe('getConfig', () => {

test('with generator and datasource', async () => {
const config = await getConfig({
ignoreEnvVarErrors: false,
datamodel: `
datasource db {
url = "file:dev.db"
Expand Down Expand Up @@ -64,6 +66,7 @@ describe('getConfig', () => {
process.env.TEST_POSTGRES_URI_FOR_DATASOURCE = 'postgres://user:password@something:5432/db'

const config = await getConfig({
ignoreEnvVarErrors: false,
datamodel: `
datasource db {
provider = "postgresql"
Expand All @@ -90,6 +93,7 @@ describe('getConfig', () => {
})
test('with engineType="binary"', async () => {
const binaryConfig = await getConfig({
ignoreEnvVarErrors: false,
datamodel: `
datasource db {
provider = "sqlite"
Expand Down Expand Up @@ -141,6 +145,7 @@ Object {
})
test('with engineType="library"', async () => {
const libraryConfig = await getConfig({
ignoreEnvVarErrors: false,
datamodel: `
datasource db {
provider = "sqlite"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('lint valid schema with a deprecated preview feature', () => {
}

test('should return a deprecated preview feature warning', () => {
const lintDiagnostics = lintSchema(schema)
const lintDiagnostics = lintSchema({ schema })

expect(ctx.mocked['console.log'].mock.calls.join('\n')).toMatchInlineSnapshot(`""`)
expect(ctx.mocked['console.warn'].mock.calls.join('\n')).toMatchInlineSnapshot(`""`)
Expand Down Expand Up @@ -83,7 +83,7 @@ Either choose another referential action, or make the referenced fields optional
}

test('should return a parsing error and a deprecated preview feature warning', () => {
const lintDiagnostics = lintSchema(schema)
const lintDiagnostics = lintSchema({ schema })

expect(ctx.mocked['console.log'].mock.calls.join('\n')).toMatchInlineSnapshot(`""`)
expect(ctx.mocked['console.warn'].mock.calls.join('\n')).toMatchInlineSnapshot(`""`)
Expand Down