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

Adding tests and allow linting TSX files #456

Merged
merged 3 commits into from Feb 3, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 2 additions & 2 deletions index.js
Expand Up @@ -121,7 +121,7 @@ module.exports = {
},
overrides: [
{
files: ['**/*.ts'],
files: ['**/*.ts', '**/*.tsx'],
extends: [
'@vue/eslint-config-typescript/recommended',
'plugin:import/typescript',
Expand All @@ -148,7 +148,7 @@ module.exports = {
'import/resolver': {
node: {
paths: ['src'],
extensions: ['.js', '.ts', '.vue'],
extensions: ['.(m|c)?js', '.ts', '.tsx', '.vue'],
},
},
},
Expand Down
15,438 changes: 11,166 additions & 4,272 deletions package-lock.json

Large diffs are not rendered by default.

15 changes: 13 additions & 2 deletions package.json
Expand Up @@ -9,7 +9,8 @@
},
"scripts": {
"lint": "eslint -c index.js *.js",
"lint:fix": "eslint -c index.js --fix *.js"
"lint:fix": "eslint -c index.js --fix *.js",
"test": "jest"
},
"peerDependencies": {
"@babel/core": "^7.13.10",
Expand All @@ -31,15 +32,18 @@
"@babel/core": "^7.13.10",
"@babel/eslint-parser": "^7.16.5",
"@nextcloud/eslint-plugin": "^2.0.0",
"@types/jest": "^29.4.0",
"@vue/eslint-config-typescript": "^11.0.2",
"eslint": "^8.27.0",
"eslint-import-resolver-exports": "^1.0.0-beta.4",
"eslint-config-standard": "^17.0.0",
"eslint-import-resolver-exports": "^1.0.0-beta.4",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsdoc": "^39.6.2",
"eslint-plugin-n": "^15.5.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-vue": "^9.7.0",
"jest": "^29.4.1",
"ts-jest": "^29.0.5",
"typescript": "^4.9.4",
"webpack": "^5.4.0"
},
Expand All @@ -59,5 +63,12 @@
"engines": {
"node": "^16.0.0",
"npm": "^7.0.0 || ^8.0.0"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"setupFilesAfterEnv": [
"./tests/setup-jest.ts"
]
}
}
31 changes: 31 additions & 0 deletions tests/eslint-config.test.ts
@@ -0,0 +1,31 @@
import { ESLint } from "eslint"
import type { Linter } from "eslint"
import * as path from 'path'
import * as eslintConfig from '../index.js'


const eslint = new ESLint({
baseConfig: eslintConfig as unknown as Linter.Config<Linter.RulesRecord>
})

const lintFile = async (file) => {
const real = path.resolve(path.join(__dirname, file))
return await eslint.lintFiles(real)
}

test('some basic issues should fail', async () => {
const results = await lintFile('fixtures/example-fail.js')
expect(results).toHaveIssueCount(3)
expect(results).toHaveIssue('spaced-comment')
expect(results).toHaveIssue({ ruleId: 'no-console', line: 3 })
})

test('TSX is linted', async () => {
const ignored = await eslint.isPathIgnored('./fixtures/some.tsx')
expect(ignored).toBe(false)

const results = await lintFile('fixtures/some.tsx')
expect(results).toHaveIssue({ruleId: 'jsdoc/check-tag-names', line: 5})
expect(results).toHaveIssue({ruleId: '@typescript-eslint/no-unused-vars', line: 7})
expect(results).toHaveIssueCount(2)
})
11 changes: 11 additions & 0 deletions tests/eslint.d.ts
@@ -0,0 +1,11 @@
export { }

declare global {
namespace jest {
interface Matchers<R> {
toPass(): R;
toHaveIssueCount(n: number): R;
toHaveIssue(issue: string | {ruleId: string, line?: number}): R;
}
}
}
3 changes: 3 additions & 0 deletions tests/fixtures/example-fail.js
@@ -0,0 +1,3 @@
//should fail because of missing space on this comment
// and usage of `console` and using a semicolon
console.log('some');
7 changes: 7 additions & 0 deletions tests/fixtures/some.tsx
@@ -0,0 +1,7 @@
// TSX

/**
* @notExported <- should work
* @aaabbbcccddd <- should fail
*/
const foo = '' // <- should fail, as unused
128 changes: 128 additions & 0 deletions tests/setup-jest.ts
@@ -0,0 +1,128 @@
import { ESLint, Linter } from "eslint"

/**
* Add some custom matchers for ESLint to jest
*/
expect.extend({
toPass: assertLintingPassed,
toHaveIssueCount: assertHavingNIssues,
toHaveIssue: assertHavingIssue
})

/**
* Check if linting a file did not throw any errors or warnings
*
* @param received Lint result
*/
function hasNoIssues(received: ESLint.LintResult) {
// dirty type check
if (received?.errorCount === undefined) throw new Error('Expected ESLintResult')

return received.errorCount === 0 && received.warningCount === 0
}

/**
* Check if linting of multiple fils
*
* @param received
* @returns {}
*/
function assertLintingPassed(received: ESLint.LintResult | ESLint.LintResult[]) {
// allow single ESLintResult
if (!Array.isArray(received)) {
received = [received]
}

const errors = [] as {file: string, errors: Linter.LintMessage[]}[]
const pass = received.every((result) => {
// save issues
errors.push({file: result.filePath, errors: result.messages})
return hasNoIssues(result)
})

return {
pass,
message: () => {
if (pass) {
return 'Expected file to not pass eslint, but got no issues'
} else {
const errorMessages = errors.map((m) =>
`file: ${m.file}\n` + m.errors.map((e) => 'line: ' + e.line + ': ' + (e.ruleId || e.message))
)
return 'Expected file to pass eslint, got issues:\n' + errorMessages.join('\n')
}
}
}
}

/**
* Count the total amount of issues
*
* @param received lint result
* @returns total amount of issues
*/
function countIssues(received: ESLint.LintResult) {
return received.errorCount + received.warningCount
}

/**
* Check if exactly the same number of issues is reported
*
* @param received the lint result
* @param expected number of expected issues
* @returns jest matcher result
*/
function assertHavingNIssues(received: ESLint.LintResult | ESLint.LintResult[], expected: number) {
if (!(typeof expected === 'number')) throw new Error('Expected a number as expected value')

if (!Array.isArray(received)) {
received = [received]
}

const issues = received.map(countIssues).reduce((p, c) => p + c)
const pass = issues === expected

return {
pass,
message: () => pass ? `Expected not to find exactly ${expected} issues.` : `Expected ${expected} issues, found ${issues}`
}
}

/**
* Check if result contains the expected issue
*
* @param received the lint result
* @param issue the expected issue
* @returns jest matcher result
*/
function assertHavingIssue(received: ESLint.LintResult | ESLint.LintResult[], issue: string | {ruleId: string, line?: number}) {
if (!Array.isArray(received)) {
received = [received]
}

// Should not pass
if (assertLintingPassed(received).pass) {
return {
pass: false,
message: () => 'Expected issue, but no linting issues found.'
}
}

const name = typeof issue === 'string' ? issue : issue.ruleId
const result = received.some((result) => {
return result.messages.some((message) => {
// ensure name matches
if (message.ruleId !== name) return false
// if line is requested ignore not matching ones
if (typeof issue === 'object' && issue.line !== undefined && issue.line !== message.line) return false
// otherwise matched
return true
})
});

const onLine = typeof issue === 'string' ? '' : ` on line ${issue.line}`
return {
pass: result,
message: () => result ? `Unexpected error '${name}'${onLine} found.` : `Expected error '${name}'${onLine} not found.`
}
}