Files
cappuccino/Tests/DetectMissingImports.js
T
2011-01-23 08:43:46 -08:00

225 lines
8.0 KiB
JavaScript

var FILE = require("file"),
FileList = require("jake").FileList,
OS = require("os"),
stream = require("narwhal/term").stream;
var cPreprocessedFileContents = function(aFilePath)
{
var INCLUDES = new FileList(FILE.join(FILE.dirname(aFilePath), "**", "*.h")).map(function(aFilename)
{
return "--include \"" + aFilename + "\"";
}).join(" ");
var gcc = OS.popen("gcc -E -x c -P -DPLATFORM_COMMONJS " + INCLUDES + " " + OS.enquote(aFilePath), { charset:"UTF-8" }),
chunk,
fileContents = "";
while (chunk = gcc.stdout.read())
fileContents += chunk;
return fileContents;
};
var checkImportsForFile = function (fileName)
{
var classNames = [],
errorMessageHashSet = {},
matches = fileName.match(new RegExp("([^\\/]+)\\/([^\\/]+)\\.j$"));
if (!FILE.exists(fileName) ||
FILE.isDirectory(fileName) ||
fileName.indexOf("+") !== -1 ||
(matches && matches[1] === matches[2]))
return;
// this is a hackish way to clear the previous file's @imports
for (var g in global)
if (g !== "SyntaxError")
delete global[g];
// reload Objective-J and get a reference to its StaticResource export at
// the same time
global.StaticResource = require.force("objective-j").StaticResource;
ObjectiveJ.OBJJ_INCLUDE_PATHS = [FILE.canonical(".")];
var og_resolveResourceAtURL = StaticResource.resolveResourceAtURL;
// this function gets called for all "File.j" imports
// in case the import can't be resolved, use JAKE.FileList to try and find
// it.
StaticResource.resolveResourceAtURL = function(/*CFURL|String*/ aURL, /*BOOL*/ isDirectory, /*Function*/ aCallback)
{
var og_callback = arguments[2];
arguments[2] = function (resource)
{
if (!resource)
{
var basename = FILE.basename(aURL.toString()),
newURL = (new FileList("Foundation/**/" + basename)).include("AppKit/**/" + basename).items()[0];
if (newURL)
return og_resolveResourceAtURL(FILE.canonical(newURL), isDirectory, og_callback)
else
og_callback(resource);
}
else
og_callback(resource);
};
og_resolveResourceAtURL.apply(this, arguments);
};
// this function gets called for <Framework/File.j> imports
// since this script looks at the un-compiled frameworks,
// some trickery for finding the correct file is sometimes necessary
var resolveResourceAtURLSearchingIncludeURLs = function (/*CFURL*/ aURL, /*Number*/ anIndex, /*Function*/ aCallback)
{
var includeURLs = StaticResource.includeURLs(),
searchURL = new CFURL(aURL, includeURLs[anIndex]).absoluteURL();
og_resolveResourceAtURL(searchURL, NO, function(/*StaticResource*/ aStaticResource)
{
if (!aStaticResource)
{
if (anIndex + 1 < includeURLs.length)
resolveResourceAtURLSearchingIncludeURLs(aURL, anIndex + 1, aCallback);
else
{
var basename = FILE.basename(aURL),
newURL = (new FileList("Foundation/**/" + basename)).include("AppKit/**/" + basename).items()[0];
if (newURL)
resolveResourceAtURLSearchingIncludeURLS(FILE.canonical(newURL), anIndex, aCallback);
else
aCallback(NULL);
}
return;
}
aCallback(aStaticResource);
});
}
StaticResource.resolveResourceAtURLSearchingIncludeURLs = function(/*CFURL*/ aURL, /*Function*/ aCallback)
{
resolveResourceAtURLSearchingIncludeURLs(aURL, 0, aCallback);
};
// find all the @implementations by running through the CPP and then
// calling ObjectiveJ.preprocess with a decorated @implementation handler
// on the Preprocessor
var fileNameContents = cPreprocessedFileContents(fileName);
var og_objj_implementation = ObjectiveJ.Preprocessor.prototype.implementation;
ObjectiveJ.Preprocessor.prototype.implementation = function (tokens)
{
var className = tokens.skip_whitespace();
if (classNames.indexOf(className) === -1)
classNames.push(className);
tokens.skip_whitespace(YES);
og_objj_implementation.apply(this, arguments);
};
ObjectiveJ.preprocess(fileNameContents);
// reset the preprocessor's @implementation handler
ObjectiveJ.Preprocessor.prototype.implementation = og_objj_implementation;
// do automatic CPP runs on @imported files
// to resolve #includes and conditional compilation
CFHTTPRequest.prototype.responseText = function ()
{
if (this._URL.slice(-2) === ".j")
{
var tmpFile = FILE.canonical(FILE.join(FILE.dirname(this._URL.split(":")[1]), ".detect_missing_imports"));
// hackishly give the file a window object
FILE.write(tmpFile, "window = {};\n" + this._nativeRequest.responseText);
var result = cPreprocessedFileContents(tmpFile);
FILE.remove(tmpFile);
}
else
result = this._nativeRequest.responseText;
return result;
};
try
{
objj_importFile(FILE.canonical(fileName), YES);
}
catch (e)
{
if (e.name === "ReferenceError")
stream.print("\0yellow(Detected missing @import in \"" + fileName + "\": " + e.message + "\0)");
else
throw e;
}
for(var j = 0, jj = classNames.length; j < jj; ++j)
{
var className = classNames[j],
cls = objj_getClass(className);
if (cls && cls.super_class)
{
// instance methods
var method_dtable = cls.method_dtable;
for (var methodName in method_dtable)
{
if (method_dtable.hasOwnProperty(methodName))
{
try
{
var instance = objj_msgSend(cls, "alloc");
objj_msgSend(instance, methodName, method_dtable[methodName].types.slice(1));
}
catch (e)
{
if (e.name === "ReferenceError")
{
var hashKey = fileName + e.message;
if (!errorMessageHashSet[hashKey])
{
stream.print("\0yellow(Detected missing @import in \"" + fileName + "\": " + e.message + " (Context: [" + className + " " + methodName + "])\0)");
errorMessageHashSet[hashKey] = true;
}
}
}
}
}
// class methods
method_dtable = cls.isa.method_dtable;
for (var methodName in method_dtable)
{
if (method_dtable.hasOwnProperty(methodName))
{
try
{
objj_msgSend(cls, methodName);
}
catch (e)
{
if (e.name === "ReferenceError")
{
var hashKey = fileName + e.message;
if (!errorMessageHashSet[hashKey])
{
stream.print("\0yellow(Detected missing @import in \"" + fileName + "\": " + e.message + " (Context: [" + className + " " + methodName + "])\0)");
errorMessageHashSet[hashKey] = true;
}
}
}
}
}
}
}
};
(new FileList("Foundation/**.j").include("AppKit/**.j")).forEach(function (aFile)
{
checkImportsForFile(aFile);
});