public BlockScope(ActivationObject parent, Context context, CodeSettings settings) : base(parent, settings) { if (context == null) { throw new ArgumentNullException("context"); } m_context = context.Clone(); }
internal FunctionScope(ActivationObject parent, bool isExpression, CodeSettings settings, FunctionObject funcObj) : base(parent, settings) { m_refScopes = new HashSet<ActivationObject>(); if (isExpression) { // parent scopes automatically reference enclosed function expressions AddReference(Parent); } FunctionObject = funcObj; }
public static void Apply(AstNode node, ActivationObject scope, CodeSettings settings) { if (node != null && scope != null) { // create the visitor and run it. This will create all the child // scopes and populate all the scopes with the var-decl, lex-decl, // and lookup references within them. var visitor = new ResolutionVisitor(scope, settings); node.Accept(visitor); // now that all the scopes are created and they all know what decls // they contains, create all the fields CreateFields(scope); // now that all the fields have been created in all the scopes, // let's go through and resolve all the references ResolveLookups(scope, settings); // now that everything is declared and resolved as per the language specs, // we need to go back and add ghosted fields for older versions of IE that // incorrectly implement catch-variables and named function expressions. AddGhostedFields(scope); } }
public WithScope(ActivationObject parent, Context context, CodeSettings settings) : base(parent, context, settings) { IsInWithScope = true; }
private ResolutionVisitor(ActivationObject rootScope, CodeSettings settings) { // create the lexical and variable scope stacks and push the root scope onto them m_lexicalStack = new Stack<ActivationObject>(); m_lexicalStack.Push(rootScope); m_variableStack = new Stack<ActivationObject>(); m_variableStack.Push(rootScope); m_settings = settings; }
private static void ResolveLookup(ActivationObject scope, Lookup lookup, CodeSettings settings) { // resolve lookup via the lexical scope lookup.VariableField = scope.FindReference(lookup.Name); if (lookup.VariableField.FieldType == FieldType.UndefinedGlobal) { // couldn't find it. // if the lookup isn't generated and isn't the object of a typeof operator, // then we want to throw an error. if (!lookup.IsGenerated) { var parentUnaryOp = lookup.Parent as UnaryOperator; if (parentUnaryOp != null && parentUnaryOp.OperatorToken == JSToken.TypeOf) { // this undefined lookup is the target of a typeof operator. // I think it's safe to assume we're going to use it. Don't throw an error // and instead add it to the "known" expected globals of the global scope MakeExpectedGlobal(lookup.VariableField); } else { // report this undefined reference lookup.Context.ReportUndefined(lookup); // possibly undefined global (but definitely not local). // see if this is a function or a variable. var callNode = lookup.Parent as CallNode; var isFunction = callNode != null && callNode.Function == lookup; lookup.Context.HandleError((isFunction ? JSError.UndeclaredFunction : JSError.UndeclaredVariable), false); } } } else if (lookup.VariableField.FieldType == FieldType.Predefined) { if (string.CompareOrdinal(lookup.Name, "window") == 0) { // it's the global window object // see if it's the child of a member or call-brackets node var member = lookup.Parent as Member; if (member != null) { // we have window.XXXX. Add XXXX to the known globals if it // isn't already a known item. scope.AddGlobal(member.Name); } else { var callNode = lookup.Parent as CallNode; if (callNode != null && callNode.InBrackets && callNode.Arguments.Count == 1 && callNode.Arguments[0] is ConstantWrapper && callNode.Arguments[0].FindPrimitiveType() == PrimitiveType.String) { // we have window["XXXX"]. See if XXXX is a valid identifier. // TODO: we could get rid of the ConstantWrapper restriction and use an Evaluate visitor // to evaluate the argument, since we know for sure that it's a string. var identifier = callNode.Arguments[0].ToString(); if (JSScanner.IsValidIdentifier(identifier)) { // Add XXXX to the known globals if it isn't already a known item. scope.AddGlobal(identifier); } } } } else if (settings.EvalTreatment != EvalTreatment.Ignore && string.CompareOrdinal(lookup.Name, "eval") == 0) { // it's an eval -- but are we calling it? // TODO: what if we are assigning it to a variable? Should we track that variable and see if we call it? // What about passing it as a parameter to a function? track that as well in case the function calls it? var parentCall = lookup.Parent as CallNode; if (parentCall != null && parentCall.Function == lookup) { scope.IsKnownAtCompileTime = false; } } } // add the reference lookup.VariableField.AddReference(lookup); // we are actually referencing this field, so it's no longer a placeholder field if it // happens to have been one. lookup.VariableField.IsPlaceholder = false; }
public SwitchParser(CodeSettings scriptSettings, CssSettings cssSettings) { // apply the switches to these two settings objects JSSettings = scriptSettings ?? new CodeSettings(); CssSettings = cssSettings ?? new CssSettings(); }
protected ActivationObject(ActivationObject parent, CodeSettings codeSettings) { m_isKnownAtCompileTime = true; m_useStrict = false; m_settings = codeSettings; Parent = parent; NameTable = new Dictionary<string, JSVariableField>(); ChildScopes = new List<ActivationObject>(); // if our parent is a scope.... if (parent != null) { // add us to the parent's list of child scopes parent.ChildScopes.Add(this); // if the parent is strict, so are we UseStrict = parent.UseStrict; } // create the two lists of declared items for this scope ScopeLookups = new HashSet<Lookup>(); VarDeclaredNames = new HashSet<INameDeclaration>(); LexicallyDeclaredNames = new HashSet<INameDeclaration>(); GhostedCatchParameters = new HashSet<ParameterDeclaration>(); GhostedFunctions = new HashSet<FunctionObject>(); }
/// <summary> /// Minifies the CSS stylesheet passes to it using the given settings, returning the minified results /// The ErrorList property will be set with any errors found during the minification process. /// </summary> /// <param name="source">CSS Source</param> /// <param name="settings">CSS minification settings</param> /// <param name="scriptSettings">JS minification settings to use for expression-minification</param> /// <returns>Minified StyleSheet</returns> public string MinifyStyleSheet(string source, CssSettings settings, CodeSettings scriptSettings) { // initialize some values, including the error list (which shoudl start off empty) string minifiedResults = string.Empty; m_errorList = new List<ContextError>(); // create the parser object and if we specified some settings, // use it to set the Parser's settings object CssParser parser = new CssParser(); parser.FileContext = FileName; if (settings != null) { parser.Settings = settings; } if (scriptSettings != null) { parser.JSSettings = scriptSettings; } // hook the error handler parser.CssError += new EventHandler<CssErrorEventArgs>(OnCssError); // try parsing the source and return the results try { minifiedResults = parser.Parse(source); } catch (Exception e) { m_errorList.Add(new ContextError( true, 0, null, null, null, this.FileName, 0, 0, 0, 0, e.Message)); throw; } return minifiedResults; }
internal CatchScope(ActivationObject parent, Context catchContext, CodeSettings settings, ParameterDeclaration catchParameter) : base(parent, catchContext, settings) { CatchParameter = catchParameter; }
public Block ParseExpression(CodeSettings settings) { // we need to make sure the settings object has the expression source mode property set, // but let's not modify the settings object passed in. So clone it, set the property on the // clone, and use that object for parsing. settings = settings == null ? new CodeSettings() : settings.Clone(); settings.SourceMode = JavaScriptSourceMode.Expression; return Parse(settings); }
/// <summary> /// Parse the source code using the given settings, getting back an abstract syntax tree Block node as the root /// representing the list of statements in the source code. /// </summary> /// <param name="settings">code settings to use to process the source code</param> /// <returns>root Block node representing the top-level statements</returns> public Block Parse(CodeSettings settings) { // initialize the scanner with our settings // make sure the RawTokens setting is OFF or we won't be able to create our AST InitializeScanner(settings); // make sure we initialize the global scope's strict mode to our flag, whether or not it // is true. This means if the setting is false, we will RESET the flag to false if we are // reusing the scope and a previous Parse call had code that set it to strict with a // program directive. GlobalScope.UseStrict = m_settings.StrictMode; // make sure the global scope knows about our known global names GlobalScope.SetAssumedGlobals(m_settings); // start of a new module m_newModule = true; Block scriptBlock; Block returnBlock; switch (m_settings.SourceMode) { case JavaScriptSourceMode.Program: // simply parse a block of statements returnBlock = scriptBlock = ParseStatements(); break; case JavaScriptSourceMode.Expression: // create a block, get the first token, add in the parse of a single expression, // and we'll go fron there. returnBlock = scriptBlock = new Block(CurrentPositionContext(), this); GetNextToken(); try { var expr = ParseExpression(); if (expr != null) { scriptBlock.Append(expr); scriptBlock.UpdateWith(expr.Context); } } catch (EndOfFileException) { Debug.WriteLine("EOF"); } break; case JavaScriptSourceMode.EventHandler: // we're going to create the global block, add in a function expression with a single // parameter named "event", and then we're going to parse the input as the body of that // function expression. We're going to resolve the global block, but only return the body // of the function. scriptBlock = new Block(null, this); var parameters = new AstNodeList(null, this); parameters.Append(new ParameterDeclaration(null, this) { Name = "event", RenameNotAllowed = true }); var funcExpression = new FunctionObject(null, this) { FunctionType = FunctionType.Expression, ParameterDeclarations = parameters }; scriptBlock.Append(funcExpression); returnBlock = ParseStatements(); funcExpression.Body = returnBlock; break; default: Debug.Fail("Unexpected source mode enumeration"); return null; } // resolve everything ResolutionVisitor.Apply(scriptBlock, GlobalScope, m_settings); if (scriptBlock != null && Settings.MinifyCode) { // this visitor doesn't just reorder scopes. It also combines the adjacent var variables, // unnests blocks, identifies prologue directives, and sets the strict mode on scopes. ReorderScopeVisitor.Apply(scriptBlock, this); // analyze the entire node tree (needed for hypercrunch) // root to leaf (top down) var analyzeVisitor = new AnalyzeNodeVisitor(this); scriptBlock.Accept(analyzeVisitor); // analyze the scope chain (also needed for hypercrunch) // root to leaf (top down) m_globalScope.AnalyzeScope(); // if we want to crunch any names.... if (m_settings.LocalRenaming != LocalRenaming.KeepAll && m_settings.IsModificationAllowed(TreeModifications.LocalRenaming)) { // then do a top-down traversal of the scope tree. For each field that had not // already been crunched (globals and outers will already be crunched), crunch // the name with a crunch iterator that does not use any names in the verboten set. m_globalScope.AutoRenameFields(); } // if we want to evaluate literal expressions, do so now if (m_settings.EvalLiteralExpressions) { var visitor = new EvaluateLiteralVisitor(this); scriptBlock.Accept(visitor); } // if any of the conditions we check for in the final pass are available, then // make the final pass if (m_settings.IsModificationAllowed(TreeModifications.BooleanLiteralsToNotOperators)) { var visitor = new FinalPassVisitor(this); scriptBlock.Accept(visitor); } // we want to walk all the scopes to make sure that any generated // variables that haven't been crunched have been assigned valid // variable names that don't collide with any existing variables. m_globalScope.ValidateGeneratedNames(); } if (returnBlock.Parent != null) { returnBlock.Parent = null; } return returnBlock; }
/// <summary> /// Preprocess the input only - don't generate a syntax tree or do any other code analysis. Just write the processed /// code to the provided text stream. /// </summary> /// <param name="settings">settings to use in the scanner</param> /// <param name="outputStream">output stream to which to write the processed source</param> public void PreprocessOnly(CodeSettings settings, TextWriter outputStream) { if (outputStream != null) { // initialize the scanner // make sure the RawTokens setting is on so that the scanner // just returns everything (after doing preprocessor evaluations) InitializeScanner(settings); // get the first token, which might be a regular expression // (since it makes no sense to start off script with a divide-operator) var scanRegExp = true; var tokenContext = m_scanner.ScanNextToken(scanRegExp); // until we hit the end of the file... int lastEndPosition = tokenContext.EndPosition; while (tokenContext.Token != JSToken.EndOfFile) { // just output the token and grab the next one. // but skip preprocessor directives! if (tokenContext.Token != JSToken.PreprocessorDirective) { outputStream.Write(tokenContext.Code); } // if this the kind of token we want to know about the next time, then save it switch (tokenContext.Token) { case JSToken.WhiteSpace: case JSToken.EndOfLine: case JSToken.AspNetBlock: case JSToken.SingleLineComment: case JSToken.MultipleLineComment: case JSToken.PreprocessorDirective: case JSToken.ConditionalCompilationOn: case JSToken.ConditionalCompilationSet: case JSToken.ConditionalCompilationIf: case JSToken.ConditionalCompilationElseIf: case JSToken.ConditionalCompilationElse: case JSToken.ConditionalCompilationEnd: // don't change the regexp flag for these tokens break; default: scanRegExp = RegExpCanFollow(tokenContext.Token); break; } tokenContext = m_scanner.ScanNextToken(scanRegExp); if (!m_scanner.IsEndOfFile && tokenContext.EndPosition == lastEndPosition) { // didn't get anything, but not at the end of the file. infinite loop? tokenContext.HandleError(JSError.ApplicationError, true); break; } else { lastEndPosition = tokenContext.EndPosition; } } } }
/// <summary> /// Preprocess the input only - don't generate an AST tree or do any other code analysis, just return the processed code as a string. /// </summary> /// <param name="settings">settings to use in the scanner</param> /// <returns>the source as processed by the preprocessor</returns> public string PreprocessOnly(CodeSettings settings) { // create an empty string builder using (var outputStream = new StringWriter(CultureInfo.InvariantCulture)) { // output to the string builder PreprocessOnly(settings, outputStream); // return the resulting text return outputStream.ToString(); } }
private void InitializeScanner(CodeSettings settings) { // save the settings // if we are passed null, just create a default settings object m_settings = settings = settings ?? new CodeSettings(); // if the settings list is not null, use it to initialize a new list // with the same settings. If it is null, initialize an empty list // because we already determined that we want to strip debug statements, // and the scanner might add items to the list as it scans the source. DebugLookups = new HashSet<string>(m_settings.DebugLookupCollection); // pass our list to the scanner -- it might add more as we encounter special comments m_scanner.DebugLookupCollection = DebugLookups; m_scanner.AllowEmbeddedAspNetBlocks = m_settings.AllowEmbeddedAspNetBlocks; m_scanner.IgnoreConditionalCompilation = m_settings.IgnoreConditionalCompilation; // set any defines m_scanner.UsePreprocessorDefines = !m_settings.IgnorePreprocessorDefines; if (m_scanner.UsePreprocessorDefines) { m_scanner.SetPreprocessorDefines(m_settings.PreprocessorValues); } // if we want to strip debug statements, let's also strip ///#DEBUG comment // blocks for legacy reasons. ///#DEBUG will get stripped ONLY is this // flag is true AND the name "DEBUG" is not in the preprocessor defines. // Alternately, we will keep those blocks in the output is this flag is // set to false OR we define "DEBUG" in the preprocessor defines. m_scanner.StripDebugCommentBlocks = m_settings.StripDebugStatements; }
/// <summary> /// Crunched JS string passed to it, returning crunched string. /// The ErrorList property will be set with any errors found during the minification process. /// </summary> /// <param name="source">source Javascript</param> /// <param name="codeSettings">code minification settings</param> /// <returns>minified Javascript</returns> public string MinifyJavaScript(string source, CodeSettings codeSettings) { // default is an empty string var crunched = string.Empty; // reset the errors builder m_errorList = new List <ContextError>(); // create the parser from the source string. // pass null for the assumed globals array var parser = new JSParser(source); // file context is a property on the parser parser.FileContext = FileName; // hook the engine error event parser.CompilerError += OnJavaScriptError; try { if (codeSettings != null && codeSettings.PreprocessOnly) { // just run through the preprocessor only crunched = parser.PreprocessOnly(codeSettings); } else { // parse the input var scriptBlock = parser.Parse(codeSettings); if (scriptBlock != null) { // we'll return the crunched code if (codeSettings != null && codeSettings.Format == JavaScriptFormat.JSON) { // we're going to use a different output visitor -- one // that specifically returns valid JSON. var sb = new StringBuilder(); using (var stringWriter = new StringWriter(sb, CultureInfo.InvariantCulture)) { if (!JSONOutputVisitor.Apply(stringWriter, scriptBlock)) { m_errorList.Add(new ContextError( true, 0, null, null, null, this.FileName, 0, 0, 0, 0, JScript.InvalidJSONOutput)); } } crunched = sb.ToString(); } else { // just use the normal output visitor crunched = scriptBlock.ToCode(); } } } } catch (Exception e) { m_errorList.Add(new ContextError( true, 0, null, null, null, this.FileName, 0, 0, 0, 0, e.Message)); throw; } return(crunched); }
/// <summary> /// Crunched JS string passed to it, returning crunched string. /// The ErrorList property will be set with any errors found during the minification process. /// </summary> /// <param name="source">source Javascript</param> /// <param name="codeSettings">code minification settings</param> /// <returns>minified Javascript</returns> public string MinifyJavaScript(string source, CodeSettings codeSettings) { // default is an empty string var crunched = string.Empty; // reset the errors builder m_errorList = new List<ContextError>(); // create the parser from the source string. // pass null for the assumed globals array var parser = new JSParser(source); // file context is a property on the parser parser.FileContext = FileName; // hook the engine error event parser.CompilerError += OnJavaScriptError; try { if (codeSettings != null && codeSettings.PreprocessOnly) { // just run through the preprocessor only crunched = parser.PreprocessOnly(codeSettings); } else { // parse the input var scriptBlock = parser.Parse(codeSettings); if (scriptBlock != null) { // we'll return the crunched code if (codeSettings != null && codeSettings.Format == JavaScriptFormat.JSON) { // we're going to use a different output visitor -- one // that specifically returns valid JSON. var sb = new StringBuilder(); using (var stringWriter = new StringWriter(sb, CultureInfo.InvariantCulture)) { if (!JSONOutputVisitor.Apply(stringWriter, scriptBlock)) { m_errorList.Add(new ContextError( true, 0, null, null, null, this.FileName, 0, 0, 0, 0, JScript.InvalidJSONOutput)); } } crunched = sb.ToString(); } else { // just use the normal output visitor crunched = scriptBlock.ToCode(); } } } } catch (Exception e) { m_errorList.Add(new ContextError( true, 0, null, null, null, this.FileName, 0, 0, 0, 0, e.Message)); throw; } return crunched; }
public SwitchParser() { // initialize with default values JSSettings = new CodeSettings(); CssSettings = new CssSettings(); // see if this is running under the Mono runtime (on UNIX) m_isMono = Type.GetType("Mono.Runtime") != null; }
private static void ResolveLookups(ActivationObject scope, CodeSettings settings) { // resolve each lookup this scope contains foreach (var lookup in scope.ScopeLookups) { ResolveLookup(scope, lookup, settings); } // and recurse foreach (var childScope in scope.ChildScopes) { ResolveLookups(childScope, settings); } }
/// <summary> /// Instantiate a new CodeSettings object with the same settings as the current object. /// </summary> /// <returns>a copy CodeSettings object</returns> public CodeSettings Clone() { // create a new settings object and set all the properties using this settings object var newSettings = new CodeSettings() { // set the field, not the property. Setting the property will set a bunch of // other properties, which may not represent their actual values. m_minify = this.m_minify, AllowEmbeddedAspNetBlocks = this.AllowEmbeddedAspNetBlocks, CollapseToLiteral = this.CollapseToLiteral, ConstStatementsMozilla = this.ConstStatementsMozilla, DebugLookupList = this.DebugLookupList, EvalLiteralExpressions = this.EvalLiteralExpressions, EvalTreatment = this.EvalTreatment, Format = this.Format, IgnoreConditionalCompilation = this.IgnoreConditionalCompilation, IgnoreAllErrors = this.IgnoreAllErrors, IgnoreErrorList = this.IgnoreErrorList, IgnorePreprocessorDefines = this.IgnorePreprocessorDefines, IndentSize = this.IndentSize, InlineSafeStrings = this.InlineSafeStrings, KillSwitch = this.KillSwitch, KnownGlobalNamesList = this.KnownGlobalNamesList, LineBreakThreshold = this.LineBreakThreshold, LocalRenaming = this.LocalRenaming, MacSafariQuirks = this.MacSafariQuirks, ManualRenamesProperties = this.ManualRenamesProperties, NoAutoRenameList = this.NoAutoRenameList, OutputMode = this.OutputMode, PreprocessOnly = this.PreprocessOnly, PreprocessorDefineList = this.PreprocessorDefineList, PreserveFunctionNames = this.PreserveFunctionNames, PreserveImportantComments = this.PreserveImportantComments, QuoteObjectLiteralProperties = this.QuoteObjectLiteralProperties, RemoveFunctionExpressionNames = this.RemoveFunctionExpressionNames, RemoveUnneededCode = this.RemoveUnneededCode, RenamePairs = this.RenamePairs, ReorderScopeDeclarations = this.ReorderScopeDeclarations, SourceMode = this.SourceMode, StrictMode = this.StrictMode, StripDebugStatements = this.StripDebugStatements, TermSemicolons = this.TermSemicolons, BlocksStartOnSameLine = this.BlocksStartOnSameLine, ErrorIfNotInlineSafe = this.ErrorIfNotInlineSafe, SymbolsMap = this.SymbolsMap, }; // set the resource strings if there are any newSettings.AddResourceStrings(this.ResourceStrings); return newSettings; }