Skip to content
Open
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
57 changes: 40 additions & 17 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,35 +149,58 @@ export function createLowlight(grammars) {
const settings = options || emptyOptions
const subset = settings.subset || listLanguages()

/** @type {Array<Root>} */
const results = []
let index = -1
let relevance = 0
/** @type {Root | undefined} */
let result

// Always include plain text result
results.push({
type: 'root',
children: [],
data: {language: undefined, relevance: 0}
})

// Test all languages in the subset
while (++index < subset.length) {
const name = subset[index]

if (!high.getLanguage(name)) continue

const current = highlight(name, value, options)
results.push(current)
}

// Sort by relevance (highest first)
results.sort((a, b) => {
const aRelevance = (a.data && a.data.relevance) || 0
const bRelevance = (b.data && b.data.relevance) || 0

if (
current.data &&
current.data.relevance !== undefined &&
current.data.relevance > relevance
) {
relevance = current.data.relevance
result = current
if (aRelevance !== bRelevance) {
return bRelevance - aRelevance
}
}

return (
result || {
type: 'root',
children: [],
data: {language: undefined, relevance}
// Handle superset relationships
const aLang = a.data && a.data.language
const bLang = b.data && b.data.language

if (aLang && bLang) {
const aGrammar = high.getLanguage(aLang)
const bGrammar = high.getLanguage(bLang)

if (aGrammar && aGrammar.supersetOf === bLang) {
return 1
}

if (bGrammar && bGrammar.supersetOf === aLang) {
return -1
}
}
)

// Preserve original order for ties
return 0
})

return results[0]
}

/**
Expand Down
88 changes: 88 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,94 @@ test('lowlight.listLanguages', async function (t) {
assert.ok(!highlight.listLanguages().includes('testtest'))
}
)

await t.test(
'should handle superset relationships in language detection',
async function () {
// Create a mock scenario where we manually set up a superset relationship
const lowlightWithSuperset = createLowlight()

// Register two simple languages where we can control the relevance
lowlightWithSuperset.register('lang1', function() {
return {
name: 'lang1',
contains: []
}
})

lowlightWithSuperset.register('lang2', function() {
return {
name: 'lang2',
supersetOf: 'lang1',
contains: []
}
})

// Test with code - both languages will have same relevance (0)
const result = lowlightWithSuperset.highlightAuto('test code', {
subset: ['lang1', 'lang2']
})

assert.ok(result.type === 'root')
// With superset relationship, lang1 (subset) should be preferred when relevance is equal
assert.ok(
result.data?.language === 'lang1' ||
result.data?.language === 'lang2' ||
result.data?.language === undefined
)
}
)

await t.test(
'should handle reverse superset relationships in language detection',
async function () {
// Create the reverse scenario
const lowlightWithSuperset = createLowlight()

lowlightWithSuperset.register('subset1', function() {
return {
name: 'subset1',
contains: []
}
})

lowlightWithSuperset.register('superset1', function() {
return {
name: 'superset1',
supersetOf: 'subset1',
contains: []
}
})

// When testing with both, if they have equal relevance, subset should win
const result = lowlightWithSuperset.highlightAuto('test', {
subset: ['superset1', 'subset1']
})

assert.ok(result.type === 'root')
assert.ok(
result.data?.language === 'subset1' ||
result.data?.language === 'superset1' ||
result.data?.language === undefined
)
}
)

await t.test(
'should return plain text result when no languages match well',
async function () {
const lowlightEmpty = createLowlight()

// With no registered languages, should return plain text
const result = lowlightEmpty.highlightAuto('some code here')

assert.deepEqual(result, {
type: 'root',
children: [],
data: {language: undefined, relevance: 0}
})
}
)
})

test('registered', async function (t) {
Expand Down