TypeScript/src/compiler/core.ts
2014-07-12 17:30:19 -07:00

535 lines
19 KiB
TypeScript

/// <reference path="types.ts"/>
module ts {
export interface Map<T> {
[index: string]: T;
}
export function forEach<T, U>(array: T[], callback: (element: T) => U): U {
var result: U;
if (array) {
for (var i = 0, len = array.length; i < len; i++) {
if (result = callback(array[i])) break;
}
}
return result;
}
export function contains<T>(array: T[], value: T): boolean {
if (array) {
var len = array.length;
for (var i = 0; i < len; i++) {
if (array[i] === value) return true;
}
}
return false;
}
export function indexOf<T>(array: T[], value: T): number {
if (array) {
var len = array.length;
for (var i = 0; i < len; i++) {
if (array[i] === value) return i;
}
}
return -1;
}
export function filter<T>(array: T[], f: (x: T) => boolean): T[] {
var result: T[];
if (array) {
result = [];
for (var i = 0, len = array.length; i < len; i++) {
var item = array[i];
if (f(item)) result.push(item);
}
}
return result;
}
export function map<T, U>(array: T[], f: (x: T) => U): U[] {
var result: U[];
if (array) {
result = [];
var len = array.length;
for (var i = 0; i < len; i++) {
result.push(f(array[i]));
}
}
return result;
}
export function concatenate<T>(array1: T[], array2: T[]): T[] {
if (!array2.length) return array1;
if (!array1.length) return array2;
return array1.concat(array2);
}
export function sum(array: any[], prop: string): number {
var result = 0;
for (var i = 0; i < array.length; i++) {
result += array[i][prop];
}
return result;
}
export function binarySearch(array: number[], value: number): number {
var low = 0;
var high = array.length - 1;
while (low <= high) {
var middle = low + ((high - low) >> 1);
var midValue = array[middle];
if (midValue === value) {
return middle;
}
else if (midValue > value) {
high = middle - 1;
}
else {
low = middle + 1;
}
}
return ~low;
}
var hasOwnProperty = Object.prototype.hasOwnProperty;
export function hasProperty<T>(map: Map<T>, key: string): boolean {
return hasOwnProperty.call(map, key);
}
export function getProperty<T>(map: Map<T>, key: string): T {
return hasOwnProperty.call(map, key) ? map[key] : undefined;
}
export function isEmpty<T>(map: Map<T>) {
for (var id in map) return false;
return true;
}
export function clone<T>(object: T): T {
var result: any = {};
for (var id in object) {
result[id] = (<any>object)[id];
}
return <T>result;
}
export function forEachValue<T, U>(map: Map<T>, callback: (value: T) => U): U {
var result: U;
for (var id in map) {
if (result = callback(map[id])) break;
}
return result;
}
export function mapToArray<T>(map: Map<T>): T[] {
var result: T[] = [];
for (var id in map) result.push(map[id]);
return result;
}
function formatStringFromArgs(text: string, args: { [index: number]: any; }, baseIndex?: number): string {
baseIndex = baseIndex || 0;
return text.replace(/{(\d+)}/g, (match, index?) => args[+index + baseIndex]);
}
export var localizedDiagnosticMessages: Map<string> = undefined;
function getLocaleSpecificMessage(message: string) {
if (ts.localizedDiagnosticMessages) {
message = localizedDiagnosticMessages[message];
}
/* Check to see that we got an actual value back. */
Debug.assert(message, "Diagnostic message does not exist in locale map.");
return message;
}
export function createFileDiagnostic(file: SourceFile, start: number, length: number, message: DiagnosticMessage, ...args: any[]): Diagnostic;
export function createFileDiagnostic(file: SourceFile, start: number, length: number, message: DiagnosticMessage): Diagnostic {
var text = getLocaleSpecificMessage(message.key);
if (arguments.length > 4) {
text = formatStringFromArgs(text, arguments, 4);
}
return {
file: file,
start: start,
length: length,
messageText: text,
category: message.category,
code: message.code
};
}
export function createCompilerDiagnostic(message: DiagnosticMessage, ...args: any[]): Diagnostic;
export function createCompilerDiagnostic(message: DiagnosticMessage): Diagnostic {
var text = getLocaleSpecificMessage(message.key);
if (arguments.length > 1) {
text = formatStringFromArgs(text, arguments, 1);
}
return {
file: undefined,
start: undefined,
length: undefined,
messageText: text,
category: message.category,
code: message.code
};
}
export function chainDiagnosticMessages(details: DiagnosticMessageChain, message: DiagnosticMessage, ...args: any[]): DiagnosticMessageChain;
export function chainDiagnosticMessages(details: DiagnosticMessageChain, message: DiagnosticMessage): DiagnosticMessageChain {
var text = getLocaleSpecificMessage(message.key);
if (arguments.length > 2) {
text = formatStringFromArgs(text, arguments, 2);
}
return {
messageText: text,
category: message.category,
code: message.code,
next: details
}
}
export function flattenDiagnosticChain(file: SourceFile, start: number, length: number, diagnosticChain: DiagnosticMessageChain): Diagnostic {
var code = diagnosticChain.code;
var category = diagnosticChain.category;
var messageText = "";
var indent = 0;
while (diagnosticChain) {
if (indent) {
messageText += sys.newLine;
for (var i = 0; i < indent; i++) {
messageText += " ";
}
}
messageText += diagnosticChain.messageText;
indent++;
diagnosticChain = diagnosticChain.next;
}
return {
file: file,
start: start,
length: length,
code: code,
category: category,
messageText: messageText
};
}
function compareValues(a: any, b: any): number {
if (a === b) return 0;
if (a === undefined) return -1;
if (b === undefined) return 1;
return a < b ? -1 : 1;
}
function getDiagnosticFilename(diagnostic: Diagnostic): string {
return diagnostic.file ? diagnostic.file.filename : undefined;
}
export function compareDiagnostics(d1: Diagnostic, d2: Diagnostic): number {
return compareValues(getDiagnosticFilename(d1), getDiagnosticFilename(d2)) ||
compareValues(d1.start, d2.start) ||
compareValues(d1.length, d2.length) ||
compareValues(d1.code, d2.code) ||
compareValues(d1.messageText, d2.messageText) ||
0;
}
export function deduplicateSortedDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {
if (diagnostics.length < 2) {
return diagnostics;
}
var newDiagnostics = [diagnostics[0]];
var previousDiagnostic = diagnostics[0];
for (var i = 1; i < diagnostics.length; i++) {
var currentDiagnostic = diagnostics[i];
var isDupe = compareDiagnostics(currentDiagnostic, previousDiagnostic) === 0;
if (!isDupe) {
newDiagnostics.push(currentDiagnostic);
previousDiagnostic = currentDiagnostic;
}
}
return newDiagnostics;
}
export function normalizeSlashes(path: string): string {
return path.replace(/\\/g, "/");
}
// Returns length of path root (i.e. length of "/", "x:/", "//server/share/")
function getRootLength(path: string): number {
if (path.charCodeAt(0) === CharacterCodes.slash) {
if (path.charCodeAt(1) !== CharacterCodes.slash) return 1;
var p1 = path.indexOf("/", 2);
if (p1 < 0) return 2;
var p2 = path.indexOf("/", p1 + 1);
if (p2 < 0) return p1 + 1;
return p2 + 1;
}
if (path.charCodeAt(1) === CharacterCodes.colon) {
if (path.charCodeAt(2) === CharacterCodes.slash) return 3;
return 2;
}
return 0;
}
export var directorySeparator = "/";
function getNormalizedParts(normalizedSlashedPath: string, rootLength: number) {
var parts = normalizedSlashedPath.substr(rootLength).split(directorySeparator);
var normalized: string[] = [];
for (var i = 0; i < parts.length; i++) {
var part = parts[i];
if (part !== ".") {
if (part === ".." && normalized.length > 0 && normalized[normalized.length - 1] !== "..") {
normalized.pop();
}
else {
normalized.push(part);
}
}
}
return normalized;
}
export function normalizePath(path: string): string {
var path = normalizeSlashes(path);
var rootLength = getRootLength(path);
var normalized = getNormalizedParts(path, rootLength);
return path.substr(0, rootLength) + normalized.join(directorySeparator);
}
export function getDirectoryPath(path: string) {
return path.substr(0, Math.max(getRootLength(path), path.lastIndexOf(directorySeparator)));
}
export function isUrl(path: string) {
return path && !isRootedDiskPath(path) && path.indexOf("://") !== -1;
}
export function isRootedDiskPath(path: string) {
return getRootLength(path) !== 0;
}
function normalizedPathComponents(path: string, rootLength: number) {
var normalizedParts = getNormalizedParts(path, rootLength);
return [path.substr(0, rootLength)].concat(normalizedParts);
}
export function getNormalizedPathComponents(path: string, currentDirectory: string) {
var path = normalizeSlashes(path);
var rootLength = getRootLength(path);
if (rootLength == 0) {
// If the path is not rooted it is relative to current directory
path = combinePaths(normalizeSlashes(currentDirectory), path);
rootLength = getRootLength(path);
}
return normalizedPathComponents(path, rootLength);
}
export function getNormalizedPathFromPathCompoments(pathComponents: string[]) {
if (pathComponents && pathComponents.length) {
return pathComponents[0] + pathComponents.slice(1).join(directorySeparator);
}
}
function getNormalizedPathComponentsOfUrl(url: string) {
// Get root length of http://www.website.com/folder1/foler2/
// In this example the root is: http://www.website.com/
// normalized path components should be ["http://www.website.com/", "folder1", "folder2"]
var urlLength = url.length;
// Initial root length is http:// part
var rootLength = url.indexOf("://") + "://".length;
while (rootLength < urlLength) {
// Consume all immediate slashes in the protocol
// eg.initial rootlength is just file:// but it needs to consume another "/" in file:///
if (url.charCodeAt(rootLength) === CharacterCodes.slash) {
rootLength++;
}
else {
// non slash character means we continue proceeding to next component of root search
break;
}
}
// there are no parts after http:// just return current string as the pathComponent
if (rootLength === urlLength) {
return [url];
}
// Find the index of "/" after website.com so the root can be http://www.website.com/ (from existing http://)
var indexOfNextSlash = url.indexOf(directorySeparator, rootLength);
if (indexOfNextSlash !== -1) {
// Found the "/" after the website.com so the root is length of http://www.website.com/
// and get components afetr the root normally like any other folder components
rootLength = indexOfNextSlash + 1;
return normalizedPathComponents(url, rootLength);
}
else {
// Can't find the host assume the rest of the string as component
// but make sure we append "/" to it as root is not joined using "/"
// eg. if url passed in was http://website.com we want to use root as [http://website.com/]
// so that other path manipulations will be correct and it can be merged with relative paths correctly
return [url + directorySeparator];
}
}
function getNormalizedPathOrUrlComponents(pathOrUrl: string, currentDirectory: string) {
if (isUrl(pathOrUrl)) {
return getNormalizedPathComponentsOfUrl(pathOrUrl);
}
else {
return getNormalizedPathComponents(pathOrUrl, currentDirectory);
}
}
export function getRelativePathToDirectoryOrUrl(directoryPathOrUrl: string, relativeOrAbsolutePath: string, currentDirectory: string, isAbsolutePathAnUrl: boolean) {
var pathComponents = getNormalizedPathOrUrlComponents(relativeOrAbsolutePath, currentDirectory);
var directoryComponents = getNormalizedPathOrUrlComponents(directoryPathOrUrl, currentDirectory);
if (directoryComponents.length > 1 && directoryComponents[directoryComponents.length - 1] === "") {
// If the directory path given was of type test/cases/ then we really need components of directry to be only till its name
// that is ["test", "cases", ""] needs to be actually ["test", "cases"]
directoryComponents.length--;
}
// Find the component that differs
for (var joinStartIndex = 0; joinStartIndex < pathComponents.length && joinStartIndex < directoryComponents.length; joinStartIndex++) {
if (directoryComponents[joinStartIndex] !== pathComponents[joinStartIndex]) {
break;
}
}
// Get the relative path
if (joinStartIndex) {
var relativePath = "";
var relativePathComponents = pathComponents.slice(joinStartIndex, pathComponents.length);
for (; joinStartIndex < directoryComponents.length; joinStartIndex++) {
if (directoryComponents[joinStartIndex] !== "") {
relativePath = relativePath + ".." + directorySeparator;
}
}
return relativePath + relativePathComponents.join(directorySeparator);
}
// Cant find the relative path, get the absolute path
var absolutePath = getNormalizedPathFromPathCompoments(pathComponents);
if (isAbsolutePathAnUrl && isRootedDiskPath(absolutePath)) {
absolutePath = "file:///" + absolutePath;
}
return absolutePath;
}
export function getBaseFilename(path: string) {
var i = path.lastIndexOf(directorySeparator);
return i < 0 ? path : path.substring(i + 1);
}
export function combinePaths(path1: string, path2: string) {
if (!(path1 && path1.length)) return path2;
if (!(path2 && path2.length)) return path1;
if (path2.charAt(0) === directorySeparator) return path2;
if (path1.charAt(path1.length - 1) === directorySeparator) return path1 + path2;
return path1 + directorySeparator + path2;
}
export function fileExtensionIs(path: string, extension: string): boolean {
var pathLen = path.length;
var extLen = extension.length;
return pathLen > extLen && path.substr(pathLen - extLen, extLen) === extension;
}
export interface ObjectAllocator {
getNodeConstructor(kind: SyntaxKind): new () => Node;
getSymbolConstructor(): new (flags: SymbolFlags, name: string) => Symbol;
getTypeConstructor(): new (checker: TypeChecker, flags: TypeFlags) => Type;
getSignatureConstructor(): new (checker: TypeChecker) => Signature;
}
function Symbol(flags: SymbolFlags, name: string) {
this.flags = flags;
this.name = name;
this.declarations = undefined;
}
function Type(checker: TypeChecker, flags: TypeFlags) {
this.flags = flags;
}
function Signature(checker: TypeChecker) {
}
export var objectAllocator: ObjectAllocator = {
getNodeConstructor: kind => {
function Node() {
}
Node.prototype = {
kind: kind,
pos: 0,
end: 0,
flags: 0,
parent: undefined,
};
return <any>Node;
},
getSymbolConstructor: () => <any>Symbol,
getTypeConstructor: () => <any>Type,
getSignatureConstructor: () => <any>Signature
}
export enum AssertionLevel {
None = 0,
Normal = 1,
Aggressive = 2,
VeryAggressive = 3,
}
export module Debug {
var currentAssertionLevel = AssertionLevel.None;
export function shouldAssert(level: AssertionLevel): boolean {
return this.currentAssertionLevel >= level;
}
export function assert(expression: any, message?: string, verboseDebugInfo?: () => string): void {
if (!expression) {
var verboseDebugString = "";
if (verboseDebugInfo) {
verboseDebugString = "\r\nVerbose Debug Information: " + verboseDebugInfo();
}
throw new Error("Debug Failure. False expression: " + (message || "") + verboseDebugString);
}
}
export function fail(message?: string): void {
Debug.assert(false, message);
}
}
}