microsoft / microsoft/vscode-textmate

Child combinators are counted as selector specificity

Open Beginner friendly
#292 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
678
Forks
135
Avg merge
1d 14h
Merged PRs (30d)
2

Description

theme.ts stores parent scope names and > child-combinator tokens in the same array.

The specificity comparator skips > while comparing individual parent scopes, but later compares the raw array lengths. A selector can therefore gain specificity merely by containing more combinator tokens.

Incorrect behavior

The comparator says that child combinators do not affect specificity:

if (a.parentScopes[aParentIndex] === '>') {
    aParentIndex++;
}
if (b.parentScopes[bParentIndex] === '>') {
    bParentIndex++;
}

It later counts those same tokens:

return b.parentScopes.length - a.parentScopes.length;

For example, these parsed arrays contain the same parent scope names:

['string', 'source.ini']
['>', 'string', '>', 'source.ini']

The second array is longer only because it contains two operators.

Expected behavior

> should constrain matching by requiring direct ancestry. It should not add another specificity component.

When two matching selectors contain the same terminal scope and the same parent scope names, differing only in child-combinator placement, they should have equal specificity. The normal source-order tie rule should then apply.

Reproduction

This test can be added to src/tests/themes.test.ts:

test('child combinators do not add specificity', () => {
    const theme = Theme.createFromRawTheme({
        settings: [
            {
                scope: 'source.ini > string > punctuation.definition.string.begin.ini',
                settings: { foreground: '#FF0000' },
            },
            {
                scope: 'source.ini string punctuation.definition.string.begin.ini',
                settings: { foreground: '#00FF00' },
            },
        ],
    });

    const result = theme.match(ScopeStack.from(
        'source.ini',
        'string.quoted.single.ini',
        'punctuation.definition.string.begin.ini',
    ));

    assert.strictEqual(
        theme.getColorMap()[result!.foregroundId],
        '#00FF00',
    );
});

Actual result: #FF0000

Expected result: #00FF00

Both selectors match the same scope components at the same positions. The first rule wins because its internal array contains two additional > entries. Without those entries, the selectors have equal specificity and the later rule wins.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in theme.ts at the specificity comparator that skips > tokens before comparing raw parent-scope array lengths. Add the reproduction to src/tests/themes.test.ts and run it; done means child combinators constrain matching without increasing specificity, so the later equal-specificity rule produces #00FF00.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
tooling
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
86/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.