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

fix: reduce some false positive matches #23

Merged
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
080095a
fix: capture entire semver string, fix some false-positive matches
antongolub May 25, 2022
0ca6460
refactor: handle some corner cases
antongolub May 25, 2022
a950ca5
chore: code style
antongolub May 25, 2022
ede99e6
feat: export SEMVER_REGEX as const
antongolub May 25, 2022
accd47f
ci: add nodejs v18 to test matrix
antongolub May 25, 2022
ed14afd
chore: rm todo comment
antongolub May 25, 2022
dbf77b2
fix: stricter look behind
antongolub May 25, 2022
22ee5c9
test: add more tests
antongolub May 25, 2022
189550b
test: check SEMVER_REGEX export
antongolub May 25, 2022
b7360ca
chore: simplify regex
antongolub May 25, 2022
9ac980b
chore: optimize regex
antongolub May 25, 2022
28bc344
chore: up deps, code style improvements
antongolub May 25, 2022
6d2d997
chore: rm temp workarounds
antongolub May 25, 2022
ae4fade
test: extend mixing case
antongolub May 25, 2022
3525812
revert: rm SEMVER_REGEX
antongolub May 26, 2022
40ccd5e
feat: add `semverRegex` export
antongolub May 26, 2022
b18c687
refactor: optimize regex
antongolub May 29, 2022
92d7791
style: linting
antongolub May 29, 2022
c8edec8
test: add fuzz redos test
antongolub May 31, 2022
0464968
style: linting
antongolub May 31, 2022
71e41db
test: reduce fuzz-test load
antongolub Jun 2, 2022
e684e79
test: up exec time limit for buzz-test
antongolub Jun 2, 2022
e3022a1
test: up exec time limit for buzz-test
antongolub Jun 2, 2022
b20ae30
Update main.yml
sindresorhus Jun 5, 2022
97d1621
Update index.d.ts
sindresorhus Jun 5, 2022
43f7e04
Update index.js
sindresorhus Jun 5, 2022
9cb8caa
Update index.test-d.ts
sindresorhus Jun 5, 2022
167e96f
Update readme.md
sindresorhus Jun 5, 2022
7fc3ad0
Update test.js
sindresorhus Jun 5, 2022
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
7 changes: 4 additions & 3 deletions .github/workflows/main.yml
Expand Up @@ -10,10 +10,11 @@ jobs:
fail-fast: false
matrix:
node-version:
- 16
- 14
- 18
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm install
Expand Down
2 changes: 2 additions & 0 deletions index.d.ts
Expand Up @@ -19,3 +19,5 @@ semverRegex().exec('unicorn 1.0.0 rainbow')[0];
```
*/
export default function semverRegex(): RegExp;

export const SEMVER_REGEX: RegExp; // eslint-disable-line @typescript-eslint/naming-convention
4 changes: 3 additions & 1 deletion index.js
@@ -1,3 +1,5 @@
export default function semverRegex() {
return /(?:(?<=^v?|\sv?)(?:(?:0|[1-9]\d{0,9}?)\.){2}(?:0|[1-9]\d{0,9}?)(?:-(?:0|[1-9]\d*?|[\da-z-]*?[a-z-][\da-z-]*?){0,100}?(?:\.(?:0|[1-9]\d*?|[\da-z-]*?[a-z-][\da-z-]*?))*?){0,100}?(?:\+[\da-z-]+?(?:\.[\da-z-]+?)*?){0,100}?\b){1,200}?/gi;
return /(?<=^v?|\sv?)(?:(?:0|[1-9]\d{0,9}?)\.){2}(?:0|[1-9]\d{0,9})(?:-(?:0|[1-9]\d*|[\da-z-]*?[a-z-][\da-z-]*?)){0,100}?(?=$| |\+|\.)(?:(?<=-\S+)(?:\.(?:[\da-z-]*?[a-z-][\da-z-]*|0|[1-9]\d*)){1,100}?)?(?!\.)(?:\+(?:[\da-z-]+(?:\.[\da-z-]+)*){1,100}?(?!\w))?(?!\+)/gi;
}

export const SEMVER_REGEX = semverRegex();
antongolub marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 2 additions & 1 deletion index.test-d.ts
@@ -1,4 +1,5 @@
import {expectType} from 'tsd';
import semverRegex from './index.js';
import semverRegex, {SEMVER_REGEX} from './index.js';

expectType<RegExp>(semverRegex());
expectType<RegExp>(SEMVER_REGEX);
5 changes: 3 additions & 2 deletions package.json
Expand Up @@ -34,7 +34,8 @@
],
"devDependencies": {
"ava": "^4.2.0",
"tsd": "^0.14.0",
"xo": "^0.39.1"
"tsd": "^0.20.0",
"xo": "^0.49.0",
"semver": "^7.3.7"
}
}
102 changes: 76 additions & 26 deletions test.js
@@ -1,24 +1,64 @@
import test from 'ava';
import semverRegex from './index.js';
import semver from 'semver';
import semverRegex, {SEMVER_REGEX} from './index.js';

const fixtures = [
'0.0.0',
'0.10.0',
'v1.0.0',
'0.0.0-foo',
'0.0.0-foo-bar-baz',
'1.2.3-4',
'2.7.2+asdf',
'1.2.3-a.b.c.10.d.5',
'2.7.2-foo+bar',
'1.2.3-alpha.10.beta',
'1.2.3-alpha.10.beta+build.unicorn.rainbow',
'foo 0.0.0 bar 0.0.0',
'99999.99999.99999'
'99999.99999.99999',

// Pulled from https://regex101.com/r/vkijKf/1/
'0.0.4',
'1.2.3',
'10.20.30',
'1.1.2-prerelease+meta',
'1.1.2+meta',
'1.1.2+meta-valid',
'1.0.0-alpha',
'1.0.0-beta',
'1.0.0-alpha.beta',
'1.0.0-alpha.beta.1',
'1.0.0-alpha.1',
'1.0.0-alpha0.valid',
'1.0.0-alpha.va1id',
'1.0.0-alpha.0valid',
'1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay',
'1.0.0-rc.1+build.1',
'2.0.0-rc.1+build.123',
'1.2.3-beta',
'10.2.3-DEV-SNAPSHOT',
'1.2.3-SNAPSHOT-123',
'1.0.0',
'2.0.0',
'1.1.7',
'2.0.0+build.1848',
'2.0.1-alpha.1227',
'1.0.0-alpha+beta',
'1.2.3----RC-SNAPSHOT.12.9.1--.12+788',
'1.2.3----R-S.12.9.1--.12+meta',
'1.2.3----RC-SNAPSHOT.12.9.1--.12',
'1.0.0+0.build.1-rc.10000aaa-kk-0.1',
// '99999999999999999999999.999999999999999999.99999999999999999', // Too long
'1.0.0-0A.is.legal',
];

test('matches semver versions on test', t => {
for (const fixture of fixtures) {
t.regex(fixture, semverRegex());
t.true(semver.valid(fixture) !== null);

if (!fixture.startsWith('v')) { // Should we trim v prefix?
t.deepEqual(fixture.match(semverRegex()), [fixture]);
}
}

t.notRegex('0.88', semverRegex());
Expand All @@ -30,20 +70,21 @@ test('matches semver versions on test', t => {
test('returns semver on match', t => {
t.deepEqual('0.0.0'.match(semverRegex()), ['0.0.0']);
t.deepEqual('foo 0.0.0 bar 0.1.1'.match(semverRegex()), ['0.0.0', '0.1.1']);
t.deepEqual('1.2.3-alpha.10.beta'.match(semverRegex()), ['1.2.3-alpha.10.beta']);
t.deepEqual('0.0.0-foo-bar alpha.beta.1 1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788 1.0.0-alpha+beta 1.2.3----RC-SNAPSHOT.12.9.1--.12+788 1.2 1.2.3-4'.match(semverRegex()), ['0.0.0-foo-bar', '1.0.0-alpha+beta', '1.2.3----RC-SNAPSHOT.12.9.1--.12+788', '1.2.3-4']);
});

test('#7, does not return tag prefix', t => {
t.deepEqual('v0.0.0'.match(semverRegex()), ['0.0.0']);
});

test('#14, does not match sub-strings of longer semver-similar strings, respect semver@2.0.0 clause 9', t => {
// TODO: Some of these are disabled as we need to improve the regex.
const invalidStrings = [
'1',
'1.2',
// '1.2.3-0123',
// '1.2.3-0123.0123',
// '1.1.2+.123',
'1.2.3-0123',
'1.2.3-0123.0123',
'1.1.2+.123',
'+invalid',
'-invalid',
'-invalid+invalid',
Expand All @@ -57,32 +98,34 @@ test('#14, does not match sub-strings of longer semver-similar strings, respect
'alpha.',
'alpha..',
'beta',
// '1.0.0-alpha_beta',
'1.0.0-alpha_beta',
'-alpha.',
// '1.0.0-alpha..',
// '1.0.0-alpha..1',
// '1.0.0-alpha...1',
// '1.0.0-alpha....1',
// '1.0.0-alpha.....1',
// '1.0.0-alpha......1',
// '1.0.0-alpha.......1',
'1.0.0-alpha..',
'1.0.0-alpha..1',
'1.0.0-alpha...1',
'1.0.0-alpha....1',
'1.0.0-alpha.....1',
'1.0.0-alpha......1',
'1.0.0-alpha.......1',
'01.1.1',
'1.01.1',
'1.1.01',
'1.2',
// '1.2.3.DEV',
'1.2.3.DEV',
'1.2-SNAPSHOT',
// '1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788',
'1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788',
'1.2-RC-SNAPSHOT',
'-1.0.3-gamma+b7718',
'+justmeta'
// '9.8.7+meta+meta',
// '9.8.7-whatever+meta+meta',
// '99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12'
'+justmeta',
'9.8.7+meta+meta',
'9.8.7-whatever+meta+meta',
'99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12',
'1.0.0-beta@beta',
];

for (const string of invalidStrings) {
t.notRegex(string, semverRegex());
t.true(semver.valid(string) === null);
}
});

Expand All @@ -93,26 +136,27 @@ test('#18, allow 0 as numeric identifier', t => {
'1.2.0-alpha.10.beta+build.unicorn.rainbow',
'1.2.3-0.10.beta+build.unicorn.rainbow',
'1.2.3-alpha.0.beta+build.unicorn.rainbow',
'1.2.3-alpha.10.0+build.unicorn.rainbow'
'1.2.3-alpha.10.0+build.unicorn.rainbow',
]) {
t.regex(string, semverRegex());
t.true(semver.valid(string) !== null);
}
});

// If tests take longer than a second, it's stuck on this and we have catatrophic backtracking.
test('invalid version does not cause catatrophic backtracking', t => {
t.regex(
'v1.1.3-0aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$',
semverRegex()
semverRegex(),
);

const postfix = '.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'.repeat(99999);
const postfix = '.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'.repeat(99_999);
t.regex(
`v1.1.3-0aa${postfix}$`,
semverRegex()
semverRegex(),
);

for (let index = 1; index <= 50000; index++) {
for (let index = 1; index <= 50_000; index++) {
const start = Date.now();
const fixture = `0.0.0-0${'.-------'.repeat(index)}@`;
semverRegex().test(fixture);
Expand All @@ -128,3 +172,9 @@ test('invalid version does not cause catatrophic backtracking', t => {
t.true(difference < 20, `Execution time: ${difference}`);
}
});

test('SEMVER_REGEX const is exported', t => {
t.regex('0.0.0', SEMVER_REGEX);
t.is(semverRegex().toString(), SEMVER_REGEX.toString());
});