Skip to content

Commit

Permalink
Add compareFile function
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Jun 10, 2023
1 parent 3608a1a commit e63d8aa
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 43 deletions.
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export {compareMessage} from './lib/index.js'
export {compareFile, compareMessage} from './lib/index.js'
87 changes: 71 additions & 16 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,37 @@
/**
* @typedef {import('vfile').VFile} VFile
* @typedef {import('vfile-message').VFileMessage} VFileMessage
*/

/** @type {Record<string, number>} */
const severities = {true: 2, false: 1, null: 0, undefined: 0}
/**
* @template Thing
* @template Kind
* @typedef {{[Key in keyof Thing]: NonNullable<Thing[Key]> extends Kind ? Key : never}[keyof Thing]} KeysOfType
* Complex type that finds the keys of fields whose values are of a certain
* type `Kind` (such as `string`) in `Thing` (probably an object).
*/

/**
* Compare a file.
*
* @since
* 4.0.0
* @param {VFile} a
* Message.
* @param {VFile} b
* Other message.
* @returns {number}
* Order.
*/
export function compareFile(a, b) {
return compareString(a, b, 'path')
}

/**
* Compare a message.
*
* @since
* 4.0.0
* @param {VFileMessage} a
* Message.
* @param {VFileMessage} b
Expand All @@ -19,23 +43,42 @@ export function compareMessage(a, b) {
return (
compareNumber(a, b, 'line') ||
compareNumber(a, b, 'column') ||
severities[String(b.fatal)] - severities[String(a.fatal)] ||
compareBoolean(a, b, 'fatal') ||
compareString(a, b, 'source') ||
compareString(a, b, 'ruleId') ||
compareString(a, b, 'reason') ||
0
compareString(a, b, 'reason')
)
}

/**
* Compare a boolean field.
*
* @template {object} Thing
* Thing type.
* @param {Thing} a
* Left thing.
* @param {Thing} b
* Right thing.
* @param {KeysOfType<Thing, boolean>} field
* Key of boolean field.
* @returns {number}
* Order.
*/
function compareBoolean(a, b, field) {
return scoreNullableBoolean(a[field]) - scoreNullableBoolean(b[field])
}

/**
* Compare a numeric field.
*
* @param {VFileMessage} a
* Message.
* @param {VFileMessage} b
* Other message.
* @param {'column' | 'line'} field
* Key of numeric field.
* @template {object} Thing
* Thing type.
* @param {Thing} a
* Left thing.
* @param {Thing} b
* Right thing.
* @param {KeysOfType<Thing, number>} field
* Key of number field.
* @returns {number}
* Order.
*/
Expand All @@ -44,15 +87,27 @@ function compareNumber(a, b, field) {
}

/**
* @param {VFileMessage} a
* Left message.
* @param {VFileMessage} b
* Right message.
* @param {'reason' | 'ruleId' | 'source'} field
* @template {object} Thing
* Thing type.
* @param {Thing} a
* Left thing.
* @param {Thing} b
* Right thing.
* @param {KeysOfType<Thing, string>} field
* Key of string field.
* @returns {number}
* Order.
*/
function compareString(a, b, field) {
return String(a[field] || '').localeCompare(String(b[field] || ''))
}

/**
* @param {boolean | null | undefined} value
* Value
* @returns {number}
* Score.
*/
function scoreNullableBoolean(value) {
return value ? 0 : value === false ? 1 : 2
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"index.js"
],
"dependencies": {
"vfile": "^6.0.0",
"vfile-message": "^4.0.0"
},
"devDependencies": {
Expand All @@ -44,7 +45,6 @@
"remark-preset-wooorm": "^9.0.0",
"type-coverage": "^2.0.0",
"typescript": "^5.0.0",
"vfile": "^6.0.0",
"xo": "^0.54.0"
},
"scripts": {
Expand Down
85 changes: 60 additions & 25 deletions test.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import {fileURLToPath} from 'node:url'
import {VFile} from 'vfile'
import {compareMessage} from './index.js'
import {VFileMessage} from 'vfile-message'
import {compareFile, compareMessage} from './index.js'

test('sort', async function () {
test('core', async function () {
assert.deepEqual(
Object.keys(await import('./index.js')).sort(),
['compareMessage'],
['compareFile', 'compareMessage'],
'should expose the public api'
)
})

let file = new VFile()

file.message('Hotel', {line: 0, column: 0})
file.message('Foxtrot')
file.message('Alpha', {line: 3, column: 0})
file.message('Bravo', {line: 3, column: 1})
file.message('Charlie', {line: 3, column: 2})
file.message('Delta', {line: 0, column: 1})
file.message('Echo', {line: 0, column: 1})
file.message('Golf', {line: 0, column: 0})
file.message('Golf', {line: 0, column: 0})

test('compareMessage', async function () {
assert.deepEqual(
file.messages.sort(compareMessage).map(String),
[
new VFileMessage('Hotel', {line: 0, column: 0}),
new VFileMessage('Foxtrot'),
new VFileMessage('Alpha', {line: 3, column: 0}),
new VFileMessage('Bravo', {line: 3, column: 1}),
new VFileMessage('Charlie', {line: 3, column: 2}),
new VFileMessage('Delta', {line: 0, column: 1}),
new VFileMessage('Echo', {line: 0, column: 1}),
new VFileMessage('Golf', {line: 0, column: 0}),
new VFileMessage('Golf', {line: 0, column: 0})
]
.sort(compareMessage)
.map(String),
[
'1:1: Foxtrot',
'1:1: Golf',
Expand All @@ -38,18 +42,49 @@ test('sort', async function () {
'should compare on line/column, reason'
)

file = new VFile()

file.info('One', {line: 2, column: 5})
file.message('Two', {line: 2, column: 5})

try {
file.fail('Three', {line: 2, column: 5})
} catch {}
const one = new VFileMessage('One', {line: 2, column: 5})
const two = new VFileMessage('Two', {line: 2, column: 5})
const three = new VFileMessage('Three', {line: 2, column: 5})
two.fatal = false
three.fatal = true

assert.deepEqual(
file.messages.sort(compareMessage).map(String),
[one, two, three].sort(compareMessage).map(String),
['2:5: Three', '2:5: Two', '2:5: One'],
'should compare on `fatal`'
)
})

test('compareFile', async function () {
assert.deepEqual(
[
new VFile(),
new VFile(new URL(import.meta.url)),
new VFile(new URL('.', import.meta.url)),
new VFile(new URL('readme.md', import.meta.url)),
new VFile(new URL('.github/', import.meta.url)),
new VFile(new URL('.github/workflows/', import.meta.url)),
new VFile(new URL('.github/workflows/main.yml', import.meta.url)),
new VFile(new URL('node_modules', import.meta.url)),
new VFile(new URL('node_modules/', import.meta.url)),
new VFile(new URL('lib/', import.meta.url)),
new VFile(new URL('lib/index.js', import.meta.url))
]
.sort(compareFile)
.map((d) => d.path),
[
undefined,
fileURLToPath(new URL('.', import.meta.url)),
fileURLToPath(new URL('.github/', import.meta.url)),
fileURLToPath(new URL('.github/workflows/', import.meta.url)),
fileURLToPath(new URL('.github/workflows/main.yml', import.meta.url)),
fileURLToPath(new URL('lib/', import.meta.url)),
fileURLToPath(new URL('lib/index.js', import.meta.url)),
fileURLToPath(new URL('node_modules', import.meta.url)),
fileURLToPath(new URL('node_modules/', import.meta.url)),
fileURLToPath(new URL('readme.md', import.meta.url)),
fileURLToPath(new URL(import.meta.url))
],
'should compare on `path`'
)
})

0 comments on commit e63d8aa

Please sign in to comment.