Skip to content

Commit

Permalink
3 fourslash improvement to support custom lib/typeshed folders (micro…
Browse files Browse the repository at this point in the history
…soft#514)

* 3 fourslash improvement to support custom lib/typeshed folders

1. added an ability to add library code directly in fourslash test file.
2. added an ability to mount different folder as typeshed folder in virtual file system in fourslash test.
3. added an ability to mount multiple folders in virtual file system.

* addressed PR feedbacks
  • Loading branch information
heejaechang authored Feb 13, 2020
1 parent 50b0d32 commit 2aa319f
Show file tree
Hide file tree
Showing 19 changed files with 280 additions and 146 deletions.
4 changes: 2 additions & 2 deletions server/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@
"windows": {
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
}
}
},
{
"type": "node",
"name": "fourslash current file",
"request": "launch",
"args": [
"fourslashrunner.test.ts",
"fourSlashRunner.test.ts",
"-t ${fileBasenameNoExtension}",
"--config",
"jest.config.js"
Expand Down
2 changes: 1 addition & 1 deletion server/src/analyzer/importResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import {
} from '../common/pathUtils';
import { versionToString } from '../common/pythonVersion';
import * as StringUtils from '../common/stringUtils';
import { VirtualFileSystem } from '../common/vfs';
import { ImplicitImport, ImportResult, ImportType } from './importResult';
import * as PythonPathUtils from './pythonPathUtils';
import { isDunderName } from './symbolNameUtils';
import { VirtualFileSystem } from '../common/vfs';

export interface ImportedModuleDescriptor {
leadingDots: number;
Expand Down
2 changes: 1 addition & 1 deletion server/src/analyzer/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
combinePaths, getDirectoryPath, getRelativePath, makeDirectories,
normalizePath, stripFileExtension
} from '../common/pathUtils';
import { DocumentRange, doRangesOverlap, Position, Range } from '../common/textRange';
import { Duration, timingStats } from '../common/timing';
import { ModuleSymbolMap } from '../languageService/completionProvider';
import { HoverResults } from '../languageService/hoverProvider';
Expand All @@ -34,7 +35,6 @@ import { SourceFile } from './sourceFile';
import { SymbolTable } from './symbol';
import { createTypeEvaluator, TypeEvaluator } from './typeEvaluator';
import { TypeStubWriter } from './typeStubWriter';
import { Position, Range, DocumentRange, doRangesOverlap } from '../common/textRange';

const _maxImportDepth = 256;

Expand Down
17 changes: 9 additions & 8 deletions server/src/analyzer/pythonPathUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import * as child_process from 'child_process';
import { ConfigOptions } from '../common/configOptions';
import * as consts from '../common/pathConsts';
import {
combinePaths, ensureTrailingDirectorySeparator, getDirectoryPath,
getFileSystemEntries, isDirectory, normalizePath
Expand All @@ -22,7 +23,7 @@ export function getTypeShedFallbackPath(moduleDirectory?: string) {
moduleDirectory = normalizePath(moduleDirectory);
return combinePaths(getDirectoryPath(
ensureTrailingDirectorySeparator(moduleDirectory)),
'typeshed-fallback');
consts.typeshedFallback);
}

return undefined;
Expand All @@ -49,22 +50,22 @@ export function findPythonSearchPaths(fs: VirtualFileSystem, configOptions: Conf
}

if (venvPath) {
let libPath = combinePaths(venvPath, 'lib');
let libPath = combinePaths(venvPath, consts.lib);
if (fs.existsSync(libPath)) {
importFailureInfo.push(`Found path '${ libPath }'; looking for site-packages`);
importFailureInfo.push(`Found path '${ libPath }'; looking for ${ consts.sitePackages }`);
} else {
importFailureInfo.push(`Did not find '${ libPath }'; trying 'Lib' instead`);
libPath = combinePaths(venvPath, 'Lib');
if (fs.existsSync(libPath)) {
importFailureInfo.push(`Found path '${ libPath }'; looking for site-packages`);
importFailureInfo.push(`Found path '${ libPath }'; looking for ${ consts.sitePackages }`);
} else {
importFailureInfo.push(`Did not find '${ libPath }'`);
libPath = '';
}
}

if (libPath) {
const sitePackagesPath = combinePaths(libPath, 'site-packages');
const sitePackagesPath = combinePaths(libPath, consts.sitePackages);
if (fs.existsSync(sitePackagesPath)) {
importFailureInfo.push(`Found path '${ sitePackagesPath }'`);
return [sitePackagesPath];
Expand All @@ -74,11 +75,11 @@ export function findPythonSearchPaths(fs: VirtualFileSystem, configOptions: Conf

// We didn't find a site-packages directory directly in the lib
// directory. Scan for a "python*" directory instead.
const entries = getFileSystemEntries(this._fs, libPath);
const entries = getFileSystemEntries(fs, libPath);
for (let i = 0; i < entries.directories.length; i++) {
const dirName = entries.directories[i];
if (dirName.startsWith('python')) {
const dirPath = combinePaths(libPath, dirName, 'site-packages');
const dirPath = combinePaths(libPath, dirName, consts.sitePackages);
if (fs.existsSync(dirPath)) {
importFailureInfo.push(`Found path '${ dirPath }'`);
return [dirPath];
Expand All @@ -89,7 +90,7 @@ export function findPythonSearchPaths(fs: VirtualFileSystem, configOptions: Conf
}
}

importFailureInfo.push(`Did not find site-packages. Falling back on python interpreter.`);
importFailureInfo.push(`Did not find '${ consts.sitePackages }'. Falling back on python interpreter.`);
}

// Fall back on the python interpreter.
Expand Down
13 changes: 13 additions & 0 deletions server/src/common/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,16 @@ export interface MapLike<T> {
export function hasProperty(map: MapLike<any>, key: string): boolean {
return hasOwnProperty.call(map, key);
}

/**
* Convert the given value to boolean
* @param trueOrFalse string value 'true' or 'false'
*/
export function toBoolean(trueOrFalse: string): boolean {
const normalized = trueOrFalse?.trim().toUpperCase();
if (normalized === 'TRUE') {
return true;
}

return false;
}
11 changes: 11 additions & 0 deletions server/src/common/pathConsts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* pathConsts.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*
* Defines well known consts and names
*/

export const typeshedFallback = 'typeshed-fallback';
export const lib = 'lib';
export const sitePackages = 'site-packages';
99 changes: 55 additions & 44 deletions server/src/common/pathUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import { URI } from 'vscode-uri';
import { some } from './collectionUtils';
import { compareValues, Comparison, GetCanonicalFileName, identity } from './core';
import * as debug from './debug';
import { compareStringsCaseInsensitive, compareStringsCaseSensitive, equateStringsCaseInsensitive,
equateStringsCaseSensitive, getStringComparer } from './stringUtils';
import {
compareStringsCaseInsensitive, compareStringsCaseSensitive, equateStringsCaseInsensitive,
equateStringsCaseSensitive, getStringComparer
} from './stringUtils';
import { VirtualFileSystem } from './vfs';

export interface FileSpec {
Expand Down Expand Up @@ -81,7 +83,7 @@ export function getPathComponents(pathString: string) {
}

export function reducePathComponents(components: readonly string[]) {
if (!some(components)) return [];
if (!some(components)) { return []; }

// Reduce the path components by eliminating
// any '.' or '..'.
Expand Down Expand Up @@ -109,7 +111,7 @@ export function reducePathComponents(components: readonly string[]) {
}

export function combinePathComponents(components: string[]): string {
if (components.length === 0) return "";
if (components.length === 0) { return ''; }

const root = components[0] && ensureTrailingDirectorySeparator(components[0]);
return normalizeSlashes(root + components.slice(1).join(path.sep));
Expand Down Expand Up @@ -155,8 +157,7 @@ export function getFileSize(fs: VirtualFileSystem, path: string) {
if (stat.isFile()) {
return stat.size;
}
}
catch { /*ignore*/ }
} catch { /*ignore*/ }
return 0;
}

Expand Down Expand Up @@ -218,11 +219,10 @@ export function comparePaths(a: string, b: string, currentDirectory?: string | b
a = normalizePath(a);
b = normalizePath(b);

if (typeof currentDirectory === "string") {
if (typeof currentDirectory === 'string') {
a = combinePaths(currentDirectory, a);
b = combinePaths(currentDirectory, b);
}
else if (typeof currentDirectory === "boolean") {
} else if (typeof currentDirectory === 'boolean') {
ignoreCase = currentDirectory;
}
return comparePathsWorker(a, b, getStringComparer(ignoreCase));
Expand All @@ -234,16 +234,15 @@ export function comparePaths(a: string, b: string, currentDirectory?: string | b
export function containsPath(parent: string, child: string, ignoreCase?: boolean): boolean;
export function containsPath(parent: string, child: string, currentDirectory: string, ignoreCase?: boolean): boolean;
export function containsPath(parent: string, child: string, currentDirectory?: string | boolean, ignoreCase?: boolean) {
if (typeof currentDirectory === "string") {
if (typeof currentDirectory === 'string') {
parent = combinePaths(currentDirectory, parent);
child = combinePaths(currentDirectory, child);
}
else if (typeof currentDirectory === "boolean") {
} else if (typeof currentDirectory === 'boolean') {
ignoreCase = currentDirectory;
}

if (parent === undefined || child === undefined) return false;
if (parent === child) return true;
if (parent === undefined || child === undefined) { return false; }
if (parent === child) { return true; }

const parentComponents = getPathComponents(parent);
const childComponents = getPathComponents(child);
Expand Down Expand Up @@ -283,8 +282,10 @@ export function changeAnyExtension(path: string, ext: string): string;
*/
export function changeAnyExtension(path: string, ext: string, extensions: string | readonly string[], ignoreCase: boolean): string;
export function changeAnyExtension(path: string, ext: string, extensions?: string | readonly string[], ignoreCase?: boolean): string {
const pathext = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(path, extensions, ignoreCase) : getAnyExtensionFromPath(path);
return pathext ? path.slice(0, path.length - pathext.length) + (ext.startsWith(".") ? ext : "." + ext) : path;
const pathext = extensions !== undefined && ignoreCase !== undefined ?
getAnyExtensionFromPath(path, extensions, ignoreCase) : getAnyExtensionFromPath(path);

return pathext ? path.slice(0, path.length - pathext.length) + (ext.startsWith('.') ? ext : '.' + ext) : path;
}

/**
Expand Down Expand Up @@ -312,14 +313,15 @@ export function getAnyExtensionFromPath(path: string, extensions?: string | read
// Retrieves any string from the final "." onwards from a base file name.
// Unlike extensionFromPath, which throws an exception on unrecognized extensions.
if (extensions) {
return getAnyExtensionFromPathWorker(stripTrailingDirectorySeparator(path), extensions, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive);
return getAnyExtensionFromPathWorker(stripTrailingDirectorySeparator(path), extensions,
ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive);
}
const baseFileName = getBaseFileName(path);
const extensionIndex = baseFileName.lastIndexOf(".");
const extensionIndex = baseFileName.lastIndexOf('.');
if (extensionIndex >= 0) {
return baseFileName.substring(extensionIndex);
}
return "";
return '';
}

/**
Expand Down Expand Up @@ -357,13 +359,15 @@ export function getBaseFileName(pathString: string, extensions?: string | readon

// if the path provided is itself the root, then it has not file name.
const rootLength = getRootLength(pathString);
if (rootLength === pathString.length) return "";
if (rootLength === pathString.length) { return ''; }

// return the trailing portion of the path starting after the last (non-terminal) directory
// separator but not including any trailing directory separator.
pathString = stripTrailingDirectorySeparator(pathString);
const name = pathString.slice(Math.max(getRootLength(pathString), pathString.lastIndexOf(path.sep) + 1));
const extension = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(name, extensions, ignoreCase) : undefined;
const extension = extensions !== undefined && ignoreCase !== undefined ?
getAnyExtensionFromPath(name, extensions, ignoreCase) : undefined;

return extension ? name.slice(0, name.length - extension.length) : name;
}

Expand All @@ -375,11 +379,15 @@ export function getRelativePathFromDirectory(from: string, to: string, ignoreCas
* Gets a relative path that can be used to traverse between `from` and `to`.
*/
export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileName: GetCanonicalFileName): string;
export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileNameOrIgnoreCase: GetCanonicalFileName | boolean) {
debug.assert((getRootLength(fromDirectory) > 0) === (getRootLength(to) > 0), "Paths must either both be absolute or both be relative");
const getCanonicalFileName = typeof getCanonicalFileNameOrIgnoreCase === "function" ? getCanonicalFileNameOrIgnoreCase : identity;
const ignoreCase = typeof getCanonicalFileNameOrIgnoreCase === "boolean" ? getCanonicalFileNameOrIgnoreCase : false;
const pathComponents = getPathComponentsRelativeTo(fromDirectory, to, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive, getCanonicalFileName);
export function getRelativePathFromDirectory(fromDirectory: string, to: string,
getCanonicalFileNameOrIgnoreCase: GetCanonicalFileName | boolean) {

debug.assert((getRootLength(fromDirectory) > 0) === (getRootLength(to) > 0), 'Paths must either both be absolute or both be relative');
const getCanonicalFileName = typeof getCanonicalFileNameOrIgnoreCase === 'function' ? getCanonicalFileNameOrIgnoreCase : identity;
const ignoreCase = typeof getCanonicalFileNameOrIgnoreCase === 'boolean' ? getCanonicalFileNameOrIgnoreCase : false;
const pathComponents = getPathComponentsRelativeTo(fromDirectory, to, ignoreCase ?
equateStringsCaseInsensitive : equateStringsCaseSensitive, getCanonicalFileName);

return combinePathComponents(pathComponents);
}

Expand Down Expand Up @@ -505,7 +513,7 @@ export function getWildcardRegexPattern(rootPath: string, fileSpec: string): str

const escapedSeparator = getRegexEscapedSeparator();
const doubleAsteriskRegexFragment = `(${ escapedSeparator }[^${ escapedSeparator }.][^${ escapedSeparator }]*)*?`;
const reservedCharacterPattern = new RegExp(`[^\\w\\s${ escapedSeparator }]`, "g");
const reservedCharacterPattern = new RegExp(`[^\\w\\s${ escapedSeparator }]`, 'g');

// Strip the directory separator from the root component.
if (pathComponents.length > 0) {
Expand Down Expand Up @@ -616,9 +624,9 @@ export function isDiskPathRoot(path: string) {

//// Path Comparisons
function comparePathsWorker(a: string, b: string, componentComparer: (a: string, b: string) => Comparison) {
if (a === b) return Comparison.EqualTo;
if (a === undefined) return Comparison.LessThan;
if (b === undefined) return Comparison.GreaterThan;
if (a === b) { return Comparison.EqualTo; }
if (a === undefined) { return Comparison.LessThan; }
if (b === undefined) { return Comparison.GreaterThan; }

// NOTE: Performance optimization - shortcut if the root segments differ as there would be no
// need to perform path reduction.
Expand All @@ -631,7 +639,7 @@ function comparePathsWorker(a: string, b: string, componentComparer: (a: string,

// check path for these segments: '', '.'. '..'
const escapedSeparator = getRegexEscapedSeparator();
const relativePathSegmentRegExp = new RegExp(`(^|${escapedSeparator}).{0,2}($|${escapedSeparator})`);
const relativePathSegmentRegExp = new RegExp(`(^|${ escapedSeparator }).{0,2}($|${ escapedSeparator })`);

// NOTE: Performance optimization - shortcut if there are no relative path segments in
// the non-root portion of the path
Expand All @@ -656,19 +664,21 @@ function comparePathsWorker(a: string, b: string, componentComparer: (a: string,
return compareValues(aComponents.length, bComponents.length);
}

function getAnyExtensionFromPathWorker(path: string, extensions: string | readonly string[], stringEqualityComparer: (a: string, b: string) => boolean) {
if (typeof extensions === "string") {
return tryGetExtensionFromPath(path, extensions, stringEqualityComparer) || "";
function getAnyExtensionFromPathWorker(path: string, extensions: string | readonly string[],
stringEqualityComparer: (a: string, b: string) => boolean) {

if (typeof extensions === 'string') {
return tryGetExtensionFromPath(path, extensions, stringEqualityComparer) || '';
}
for (const extension of extensions) {
const result = tryGetExtensionFromPath(path, extension, stringEqualityComparer);
if (result) return result;
if (result) { return result; }
}
return "";
return '';
}

function tryGetExtensionFromPath(path: string, extension: string, stringEqualityComparer: (a: string, b: string) => boolean) {
if (!extension.startsWith(".")) extension = "." + extension;
if (!extension.startsWith('.')) { extension = '.' + extension; }
if (path.length >= extension.length && path.charCodeAt(path.length - extension.length) === Char.Period) {
const pathExtension = path.slice(path.length - extension.length);
if (stringEqualityComparer(pathExtension, extension)) {
Expand All @@ -679,7 +689,9 @@ function tryGetExtensionFromPath(path: string, extension: string, stringEquality
return undefined;
}

function getPathComponentsRelativeTo(from: string, to: string, stringEqualityComparer: (a: string, b: string) => boolean, getCanonicalFileName: GetCanonicalFileName) {
function getPathComponentsRelativeTo(from: string, to: string, stringEqualityComparer: (a: string, b: string) => boolean,
getCanonicalFileName: GetCanonicalFileName) {

const fromComponents = getPathComponents(from);
const toComponents = getPathComponents(to);

Expand All @@ -688,7 +700,7 @@ function getPathComponentsRelativeTo(from: string, to: string, stringEqualityCom
const fromComponent = getCanonicalFileName(fromComponents[start]);
const toComponent = getCanonicalFileName(toComponents[start]);
const comparer = start === 0 ? equateStringsCaseInsensitive : stringEqualityComparer;
if (!comparer(fromComponent, toComponent)) break;
if (!comparer(fromComponent, toComponent)) { break; }
}

if (start === 0) {
Expand All @@ -698,14 +710,14 @@ function getPathComponentsRelativeTo(from: string, to: string, stringEqualityCom
const components = toComponents.slice(start);
const relative: string[] = [];
for (; start < fromComponents.length; start++) {
relative.push("..");
relative.push('..');
}
return ["", ...relative, ...components];
return ['', ...relative, ...components];
}

const enum FileSystemEntryKind {
File,
Directory,
Directory
}

function fileSystemEntryExists(fs: VirtualFileSystem, path: string, entryKind: FileSystemEntryKind): boolean {
Expand All @@ -716,8 +728,7 @@ function fileSystemEntryExists(fs: VirtualFileSystem, path: string, entryKind: F
case FileSystemEntryKind.Directory: return stat.isDirectory();
default: return false;
}
}
catch (e) {
} catch (e) {
return false;
}
}
Expand Down
Loading

0 comments on commit 2aa319f

Please sign in to comment.