diff --git a/package.json b/package.json
index d50a3625c428dc07cffc49ebe1b8a74e21a6dfba..bccb2df05071830cea1acd82824f691932967db3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "mfm-js",
-  "version": "0.12.0",
+  "version": "0.0.0",
   "description": "An MFM parser implementation with PEG.js",
   "main": "./built/index.js",
   "types": "./built/index.d.ts",
diff --git a/src/parser.pegjs b/src/parser.pegjs
index 148b3ef62db3d65ba72a8d6d85378216346c807d..b517237b33da12abd58f80f6889f2490fa44f124 100644
--- a/src/parser.pegjs
+++ b/src/parser.pegjs
@@ -333,7 +333,7 @@ urlBracketPair
 // inline: link
 
 link
-	= silent:"?"? "[" label:linkLabel "](" url:linkUrl ")"
+	= silent:"?"? "[" label:linkLabelPart+ "](" url:linkUrl ")"
 {
 	return createNode('link', {
 		silent: (silent != null),
@@ -341,8 +341,10 @@ link
 	}, mergeText(label));
 }
 
-linkLabel
-	= (!"]" n:inline { return n; })+
+linkLabelPart
+	= url { return text(); /* text node */ }
+	/ link { return text(); /* text node */ }
+	/ !"]" n:inline { return n; }
 
 linkUrl
 	= url { return text(); }
@@ -382,7 +384,7 @@ fnArg
 // inline: text
 
 text
-	= .
+	= . /* text node */
 
 //
 // General
diff --git a/test/main.ts b/test/main.ts
index 909aad444e60f57576204b455ec311347029cb6f..7ec63038de46e271c4f9e221864a8d1331bb7d4c 100644
--- a/test/main.ts
+++ b/test/main.ts
@@ -2,7 +2,7 @@ import assert from 'assert';
 import { extract, inspect, parse, parsePlain, toString } from '../built/index';
 import { createNode } from '../built/util';
 import {
-	TEXT, CENTER, FN, UNI_EMOJI, MENTION, EMOJI_CODE, HASHTAG, N_URL, BOLD, SMALL, ITALIC, STRIKE, QUOTE, MATH_BLOCK, SEARCH, CODE_BLOCK
+	TEXT, CENTER, FN, UNI_EMOJI, MENTION, EMOJI_CODE, HASHTAG, N_URL, BOLD, SMALL, ITALIC, STRIKE, QUOTE, MATH_BLOCK, SEARCH, CODE_BLOCK, LINK
 } from './node';
 
 describe('text', () => {
@@ -423,7 +423,53 @@ describe('url', () => {
 	});
 });
 
-// link
+describe('link', () => {
+	it('basic', () => {
+		const input = '[official instance](https://misskey.io/@ai).';
+		const output = [
+			LINK(false, 'https://misskey.io/@ai', [
+				TEXT('official instance')
+			]),
+			TEXT('.')
+		];
+		assert.deepStrictEqual(parse(input), output);
+	});
+
+	it('silent flag', () => {
+		const input = '?[official instance](https://misskey.io/@ai).';
+		const output = [
+			LINK(true, 'https://misskey.io/@ai', [
+				TEXT('official instance')
+			]),
+			TEXT('.')
+		];
+		assert.deepStrictEqual(parse(input), output);
+	});
+
+	it('do not yield url node even if label is recognisable as a url', () => {
+		const input = 'official instance: [https://misskey.io/@ai](https://misskey.io/@ai).';
+		const output = [
+			TEXT('official instance: '),
+			LINK(false, 'https://misskey.io/@ai', [
+				TEXT('https://misskey.io/@ai')
+			]),
+			TEXT('.')
+		];
+		assert.deepStrictEqual(parse(input), output);
+	});
+
+	it('do not yield link node even if label is recognisable as a link', () => {
+		const input = 'official instance: [[https://misskey.io/@ai](https://misskey.io/@ai)](https://misskey.io/@ai).';
+		const output = [
+			TEXT('official instance: '),
+			LINK(false, 'https://misskey.io/@ai', [
+				TEXT('[https://misskey.io/@ai](https://misskey.io/@ai)')
+			]),
+			TEXT('.')
+		];
+		assert.deepStrictEqual(parse(input), output);
+	});
+});
 
 describe('fn', () => {
 	it('basic', () => {
@@ -497,10 +543,20 @@ describe('inspect API', () => {
 
 describe('extract API', () => {
 	it('basic', () => {
-		const nodes = parse('abc:hoge:[tada 123:hoge:]:piyo:');
+		const nodes = parse('@hoge @piyo @bebeyo');
+		const expect = [
+			MENTION('hoge', null, '@hoge'),
+			MENTION('piyo', null, '@piyo'),
+			MENTION('bebeyo', null, '@bebeyo')
+		];
+		assert.deepStrictEqual(extract(nodes, 'mention'), expect);
+	});
+
+	it('nested', () => {
+		const nodes = parse('abc:hoge:[tada 123 @hoge :foo:]:piyo:');
 		const expect = [
 			EMOJI_CODE('hoge'),
-			EMOJI_CODE('hoge'),
+			EMOJI_CODE('foo'),
 			EMOJI_CODE('piyo')
 		];
 		assert.deepStrictEqual(extract(nodes, 'emojiCode'), expect);