Skip to content

Commit

Permalink
Fixes nodejs#17764
Browse files Browse the repository at this point in the history
  • Loading branch information
Bamieh committed Dec 20, 2017
1 parent 3bb6f07 commit dc1c091
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 4 deletions.
61 changes: 57 additions & 4 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,57 @@ ArrayStream.prototype.resume = function() {};
ArrayStream.prototype.write = function() {};

const requireRE = /\brequire\s*\(['"](([\w@./-]+\/)?(?:[\w@./-]*))/;


const fsTabbableMethods = [
'access',
'accessSync',
'chmod',
'chmodSync',
'chown',
'chownSync',
'createReadStream',
'createWriteStream',
'exists',
'existsSync',
'lchmod',
'lchmodSync',
'lchown',
'lchownSync',
'lstat',
'lstatSync',
'mkdir',
'mkdirSync',
'open',
'openSync',
'readdir',
'readdirSync',
'readFile',
'readFileSync',
'readlink',
'readlinkSync',
'realpath',
'realpath',
'realpathSync',
'realpathSync',
'rmdir',
'rmdirSync',
'stat',
'statSync',
'symlink',
'symlinkSync',
'truncate',
'truncateSync',
'unlink',
'unlinkSync',
'utimes',
'utimesSync',
].join('|');

const fsRE = new RegExp(
`fs\\.(?:${fsTabbableMethods})\\(["'](([\\w@./-]+\\/)?(?:[\\w@./-]*))`
);

const simpleExpressionRE =
/(?:[a-zA-Z_$](?:\w|\$)*\.)*[a-zA-Z_$](?:\w|\$)*\.?$/;

Expand Down Expand Up @@ -948,8 +999,10 @@ function complete(line, callback) {
}

completionGroupsLoaded();
} else if (match = line.match(requireRE)) {
// require('...<Tab>')
} else if (match = (line.match(requireRE) || line.match(fsRE))) {
// require('...<Tab>') and fs.<method>('...<Tab>')

const isFS = line.match(fsRE);
const exts = Object.keys(this.context.require.extensions);
var indexRe = new RegExp('^index(?:' + exts.map(regexpEscape).join('|') +
')$');
Expand All @@ -968,7 +1021,7 @@ function complete(line, callback) {
group = ['../'];
} else if (/^\.\.?\//.test(completeOn)) {
paths = [process.cwd()];
} else {
} else if (!isFS) {
paths = module.paths.concat(Module.globalPaths);
}

Expand Down Expand Up @@ -1014,7 +1067,7 @@ function complete(line, callback) {
completionGroups.push(group);
}

if (!subdir) {
if (!subdir && !isFS) {
completionGroups.push(exports._builtinLibs);
}

Expand Down
133 changes: 133 additions & 0 deletions test/parallel/test-repl-tab-complete.js
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,139 @@ testMe.complete('var log = console.lo', common.mustCall((error, data) => {
assert.deepStrictEqual(data, [['console.log'], 'console.lo']);
}));

// Test tab complete for fs module
putIn.run(['.clear']);

// does not auto complete empty paths
testMe.complete('fs.readFileSync("', common.mustCall((error, data) => {
assert.strictEqual(error, null);
assert.strictEqual(data[0].length, 0);
}));

// does not complete non-relative paths
testMe.complete('fs.readFileSync("n', common.mustCall((error, data) => {
assert.strictEqual(error, null);
assert.strictEqual(data[0].length, 0);
}));

// does not complete scoped modules paths

testMe.complete('fs.readFileSync("@nodejs', common.mustCall((error, data) => {
assert.strictEqual(error, null);
assert.strictEqual(data[0].length, 0);
}));


// Test tab completion for fs module paths relative to the current directory
{
putIn.run(['.clear']);

const cwd = process.cwd();
process.chdir(__dirname);

['fs.readFileSync(\'.', 'fs.readFileSync(".'].forEach((input) => {
testMe.complete(input, common.mustCall((err, data) => {
assert.strictEqual(err, null);
assert.strictEqual(data.length, 2);
assert.strictEqual(data[1], '.');
assert.strictEqual(data[0].length, 2);
assert.ok(data[0].includes('./'));
assert.ok(data[0].includes('../'));
}));
});

['fs.readFileSync(\'..', 'fs.readFileSync("..'].forEach((input) => {
testMe.complete(input, common.mustCall((err, data) => {
assert.strictEqual(err, null);
assert.deepStrictEqual(data, [['../'], '..']);
}));
});

const fsTabbableMethods = [
'access',
'accessSync',
'chmod',
'chmodSync',
'chown',
'chownSync',
'createReadStream',
'createWriteStream',
'exists',
'existsSync',
'lchmod',
'lchmodSync',
'lchown',
'lchownSync',
'lstat',
'lstatSync',
'mkdir',
'mkdirSync',
'open',
'openSync',
'readdir',
'readdirSync',
'readFile',
'readFileSync',
'readlink',
'readlinkSync',
'realpath',
'realpath',
'realpathSync',
'realpathSync',
'rmdir',
'rmdirSync',
'stat',
'statSync',
'symlink',
'symlinkSync',
'truncate',
'truncateSync',
'unlink',
'unlinkSync',
'utimes',
'utimesSync',
];

// test that all tabbable methods allow this featuer
fsTabbableMethods.forEach((method) => {
testMe.complete(`fs.${method}('..`, common.mustCall((err, data) => {
assert.strictEqual(err, null);
assert.deepStrictEqual(data, [['../'], '..']);
}));
});

['./', './test-'].forEach((path) => {
testMe.complete(`fs.readFileSync('${path}`, common.mustCall((err, data) => {
assert.strictEqual(err, null);
assert.strictEqual(data.length, 2);
assert.strictEqual(data[1], path);
assert.ok(data[0].includes('./test-repl-tab-complete'));
}));
});

['../parallel/', '../parallel/test-'].forEach((path) => {
testMe.complete(`fs.readFileSync('${path}`, common.mustCall((err, data) => {
assert.strictEqual(err, null);
assert.strictEqual(data.length, 2);
assert.strictEqual(data[1], path);
assert.ok(data[0].includes('../parallel/test-repl-tab-complete'));
}));
});

{
const path = '../fixtures/repl-folder-extensions/f';
testMe.complete(`fs.readFileSync('${path}`, common.mustCall((err, data) => {
assert.ifError(err);
assert.strictEqual(data.length, 2);
assert.strictEqual(data[1], path);
assert.ok(data[0].includes('../fixtures/repl-folder-extensions/foo.js'));
}));
}


process.chdir(cwd);
}

// tab completion for defined commands
putIn.run(['.clear']);

Expand Down

0 comments on commit dc1c091

Please sign in to comment.