Skip to content
This repository has been archived by the owner on Sep 26, 2023. It is now read-only.

Commit

Permalink
feat: support full GitHub URLs
Browse files Browse the repository at this point in the history
  • Loading branch information
z0al committed Dec 12, 2020
1 parent ae7283e commit 483aa39
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 105 deletions.
42 changes: 28 additions & 14 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

152 changes: 83 additions & 69 deletions src/__tests__/helpers.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,12 @@
// Packages
import issueRegex from 'issue-regex';

// Ours
import { ActionContext, GithubClient, Issue } from '../types';
import {
createDependencyRegex,
DependencyExtractor,
DependencyResolver,
IssueManager,
formatDependency,
} from '../helpers';

test('createDependencyRegex', () => {
const regex = createDependencyRegex(['depends on', 'blocked by']);

expect(regex.flags).toEqual('gi');
expect(regex.source).toEqual(
`(?:depends on|blocked by)\\s+(${issueRegex().source})`
);
});

test('formatDependency', () => {
const repo = { owner: 'owner', repo: 'repo' };
const dep = { ...repo, number: 141 };
Expand All @@ -28,82 +15,109 @@ test('formatDependency', () => {
expect(formatDependency(dep, repo)).toEqual('#141');
});

describe('DependencyExtractor', () => {
test('DependencyExtractor', () => {
const repo = {
owner: 'github',
repo: 'atom',
};

const tests = [
const body = `
Should match:
- Plain issue:
- Depends on #666
- Blocked by #123
- From another repository:
- Depends on another/repo#123
- Full issue URL:
- Depends on https://github.com/another/repo/issues/141
- Depends on http://github.com/another/repo/issues/404
- Depends on https://github.com/another/repo/pulls/142
- Crazy formatting:
- Depends on ano-ther.999/re_po#123
- In brackets:
- (Depends on #486)
- [Depends on #3167]
- <Depends on another/repo#18767>
Should NOT match:
- Depends on #0
- Depends on another/repo#0
- Depends on nonrepo#123
- Depends on non/-repo#123
- Depends on user_repo#123
- Depends on this/is/not/repo#123
- Depends on #123hashtag
`;

const issue = { body } as Issue;

const expectedDeps = [
// Depends on #666
{
title: 'empty string',
text: '',
expected: [],
...repo,
number: 666,
},
// Blocked by #123
{
title: 'wrong keyword',
text: 'depends on #2',
expected: [],
...repo,
number: 123,
},
// Depends on another/repo#123
{
title: 'self-referencing',
text: 'blocked by #1',
expected: [],
owner: 'another',
repo: 'repo',
number: 123,
},
// Depends on https://github.com/another/repo/issues/141
{
title: 'multiple dependencies',
text: 'blocked by #2, blocked by #1 and blocked by #3',
expected: [
{
...repo,
number: 2,
},
{
...repo,
number: 3,
},
],
owner: 'another',
repo: 'repo',
number: 141,
},
// Depends on http://github.com/another/repo/issues/404
{
title: 'duplicated issues',
text: 'blocked by #2, blocked by #1 and blocked by #2',
expected: [
{
...repo,
number: 2,
},
],
owner: 'another',
repo: 'repo',
number: 404,
},
// Depends on https://github.com/another/repo/pulls/142
{
title: 'with full links',
text:
'blocked by github/atom#2, blocked by Microsoft/vscode#1 and blocked by #3',
expected: [
{
...repo,
number: 2,
},
{
owner: 'Microsoft',
repo: 'vscode',
number: 1,
},
{
...repo,
number: 3,
},
],
owner: 'another',
repo: 'repo',
number: 142,
},
// Depends on ano-ther.999/re_po#123
{
owner: 'ano-ther.999',
repo: 're_po',
number: 123,
},
// (Depends on #486)
{
...repo,
number: 486,
},
// [Depends on #3167]
{
...repo,
number: 3167,
},
// <Depends on another/repo#18767>
{
owner: 'another',
repo: 'repo',
number: 18767,
},
];

const extractor = new DependencyExtractor(repo, ['blocked by']);
const extractor = new DependencyExtractor(repo, [
' depends On',
'blocked by',
]);

tests.forEach((t) => {
it(t.title, () => {
const issue = { body: t.text, number: 1 } as Issue;
expect(extractor.fromIssue(issue)).toEqual(t.expected);
});
});
expect(extractor.fromIssue(issue)).toEqual(expectedDeps);
});

describe('DependencyResolver', () => {
Expand Down
55 changes: 34 additions & 21 deletions src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Packages
import { dequal } from 'dequal';
import uniqBy from 'lodash.uniqby';
import issueRegex from 'issue-regex';
import IssueRegex from 'issue-regex';

// Ours
import {
Expand All @@ -12,18 +12,6 @@ import {
ActionContext,
} from './types';

const ISSUE_REGEX = issueRegex();

export function createDependencyRegex(keywords: string[]) {
const flags = ISSUE_REGEX.flags + 'i';

// outputs: kw1|kw2 <white-space> (<issue-regex>)
return new RegExp(
`(?:${keywords.join('|')})\\s+(${ISSUE_REGEX.source})`,
flags
);
}

export function formatDependency(dep: Dependency, repo?: Repository) {
const depRepo = { owner: dep.owner, repo: dep.repo };

Expand All @@ -35,28 +23,53 @@ export function formatDependency(dep: Dependency, repo?: Repository) {
}

export class DependencyExtractor {
private pattern: RegExp;
private regex: RegExp;
private issueRegex = IssueRegex();
private urlRegex = /https?:\/\/github\.com\/(?:\w[\w-.]+\/\w[\w-.]+|\B)\/(?:issues|pulls)\/[1-9]\d*\b/;
private keywordRegex: RegExp;

constructor(private repo: Repository, keywords: string[]) {
this.pattern = createDependencyRegex(keywords);
this.keywordRegex = new RegExp(
keywords.map((kw) => kw.trim().replace(/\s+/g, '\\s+')).join('|'),
'i'
);

this.regex = this.buildRegex();
}

private buildRegex() {
const flags = this.issueRegex.flags + 'i';
const ref = `${this.issueRegex.source}|${this.urlRegex.source}`;

return new RegExp(
`(?:${this.keywordRegex.source})\\s+(${ref})`,
flags
);
}

private deduplicate(deps: Dependency[]) {
return uniqBy(deps, formatDependency);
}

private getIssueLinks(text: string) {
const issuesWithKeywords = text.match(this.pattern) || [];
private match(text: string) {
const references = text.match(this.regex) || [];

return issuesWithKeywords.map(
(issue) => issue.match(ISSUE_REGEX)?.[0] as string
);
return references.map((ref) => {
// Get rid of keywords now
ref = ref.replace(this.keywordRegex, '').trim();

// Remove full URL if found. Should return either '#number' or
// 'owner/repo#number' format
return ref
.replace(/https?:\/\/github\.com\//i, '')
.replace(/\/(issues|pulls)\//i, '#');
});
}

public fromIssue(issue: Issue) {
const dependencies: Dependency[] = [];

for (const issueLink of this.getIssueLinks(issue.body || '')) {
for (const issueLink of this.match(issue.body || '')) {
// Can be '#number' or 'owner/repo#number'
// 1) #number
if (issueLink.startsWith('#')) {
Expand Down

0 comments on commit 483aa39

Please sign in to comment.