/// <summary> /// Set the collection of defined names for the preprocessor /// </summary> /// <param name="definedNames">array of defined name strings</param> /// <returns>number of names successfully added to the collection</returns> public int SetPreprocessorDefines(params string[] definedNames) { PreprocessorValues.Clear(); if (definedNames != null && definedNames.Length > 0) { // validate that each name in the array is a valid JS identifier foreach (var define in definedNames) { string trimmedName; var ndxEquals = define.IndexOf('='); if (ndxEquals < 0) { trimmedName = define.Trim(); } else { trimmedName = define.Substring(0, ndxEquals).Trim(); } // must be a valid JS identifier if (JsScanner.IsValidIdentifier(trimmedName)) { PreprocessorValues.Add(trimmedName, ndxEquals < 0 ? string.Empty : define.Substring(ndxEquals + 1)); } } } return(PreprocessorValues.Count); }
/// <summary> /// Adds a debug lookup namespace to this settings object. /// </summary> /// <param name="debugNamespace">The debug namespace to add.</param> /// <returns>Whether it was added or not.</returns> public bool AddDebugLookup(string debugNamespace) { // a blank identifier is okay -- we just ignore it if (!string.IsNullOrEmpty(debugNamespace)) { // but if it's not blank, it better be a valid JavaScript identifier or member chain if (debugNamespace.IndexOf('.') > 0) { // it's a member chain -- check that each part is a valid JS identifier var names = debugNamespace.Split('.'); foreach (var name in names) { if (!JsScanner.IsValidIdentifier(name)) { return(false); } } } else { // no dot -- just an identifier if (!JsScanner.IsValidIdentifier(debugNamespace)) { return(false); } } m_debugLookups.Add(debugNamespace); return(true); } return(false); }
/// <summary> /// Creates an instance of the JSParser class that can be used to parse the given source code. /// </summary> /// <param name="source">Source code to parse.</param> public JsParser(string source) { m_severity = 5; m_blockType = new List<BlockType>(16); m_labelTable = new Dictionary<string, LabelInfo>(); m_noSkipTokenSet = new NoSkipTokenSet(); m_importantComments = new List<JsContext>(); m_document = new JsDocumentContext(this, source); m_scanner = new JsScanner(new JsContext(m_document)); m_currentToken = new JsContext(m_document); // if the scanner encounters a special "globals" comment, it'll fire this event // at which point we will define a field with that name in the global scope. m_scanner.GlobalDefine += (sender, ea) => { var globalScope = GlobalScope; if (globalScope[ea.Name] == null) { var field = globalScope.CreateField(ea.Name, null, FieldAttributes.SpecialName); globalScope.AddField(field); } }; // this event is fired whenever a ///#SOURCE comment is encountered m_scanner.NewModule += (sender, ea) => { m_newModule = true; // we also want to assume that we found a newline character after // the comment m_foundEndOfLine = true; }; }
/// <summary> /// Add a known global identifier to the list /// </summary> /// <param name="identifier">global identifier</param> /// <returns>true if valid identifier; false if invalid identifier</returns> public bool AddKnownGlobal(string identifier) { if (JsScanner.IsValidIdentifier(identifier)) { m_knownGlobals.Add(identifier); return(true); } return(false); }
/// <summary> /// Adds an identifier to the auto-rename exclusion list. /// </summary> /// <param name="noRename">The identifier to avoid renaming.</param> /// <returns>Whether it was added or not.</returns> public bool AddNoAutoRename(string noRename) { if (!JsScanner.IsValidIdentifier(noRename)) { return(false); } m_noRenameSet.Add(noRename); return(true); }
internal string NextName() { string name; do { // advance to the next name ++m_currentName; name = CurrentName; // keep advancing until we find one that isn't in the skip list or a keyword // (use strict mode to be safe) }while (m_skipNames.Contains(name) || JsScanner.IsKeyword(name, true)); return(name); }
/// <summary> /// Set the dictionary of preprocessor defines and values /// </summary> /// <param name="defines">dictionary to set</param> public int SetPreprocessorValues(IDictionary <string, string> defines) { PreprocessorValues.Clear(); if (defines != null && defines.Count > 0) { foreach (var define in defines) { if (JsScanner.IsValidIdentifier(define.Key)) { PreprocessorValues.Add(define.Key, define.Value); } } } return(PreprocessorValues.Count); }
private void UnreferencedFunction(JsVariableField variableField, JsFunctionObject functionObject) { // if there is no name, then ignore this declaration because it's malformed. // (won't be a function expression because those are automatically referenced). // also ignore ghosted function fields. if (functionObject.Name != null && variableField.FieldType != JsFieldType.GhostFunction) { // if the function name isn't a simple identifier, then leave it there and mark it as // not renamable because it's probably one of those darn IE-extension event handlers or something. if (JsScanner.IsValidIdentifier(functionObject.Name)) { // unreferenced function declaration. fire a warning. var ctx = functionObject.IdContext ?? variableField.OriginalContext; ctx.HandleError(JsError.FunctionNotReferenced, false); // hide it from the output if our settings say we can. // we don't want to delete it, per se, because we still want it to // show up in the scope report so the user can see that it was unreachable // in case they are wondering where it went. // ES6 has the notion of block-scoped function declarations. ES5 says functions can't // be defined inside blocks -- only at the root level of the global scope or function scopes. // so if this is a block scope, don't hide the function, even if it is unreferenced because // of the cross-browser difference. if (this.IsKnownAtCompileTime && m_settings.MinifyCode && m_settings.RemoveUnneededCode && !(this is JsBlockScope)) { functionObject.HideFromOutput = true; } } else { // not a valid identifier name for this function. Don't rename it because it's // malformed and we don't want to mess up the developer's intent. variableField.CanCrunch = false; } } }
public bool Match(JsAstNode node, string identifiers) { // set the match to false m_isMatch = false; // identifiers cannot be null or blank and must match: IDENT(.IDENT)* // since for JS there has to be at least a global object, the dot must be AFTER the first character. if (node != null && !string.IsNullOrEmpty(identifiers)) { // get all the parts var parts = identifiers.Split('.'); // each part must be a valid JavaScript identifier. Assume everything is valid // unless at least one is invalid -- then forget it var isValid = true; foreach (var part in parts) { if (!JsScanner.IsValidIdentifier(part)) { isValid = false; break; } } // must be valid to continue if (isValid) { // save the parts and start the index on the last one, since we'll be walking backwards m_parts = parts; m_index = parts.Length - 1; node.Accept(this); } } return(m_isMatch); }
/// <summary> /// Add a rename pair to the identifier rename map /// </summary> /// <param name="sourceName">name of the identifier in the source code</param> /// <param name="newName">new name with which to replace the source name</param> /// <returns>true if added; false if either name is not a valid JavaScript identifier</returns> public bool AddRenamePair(string sourceName, string newName) { bool successfullyAdded = false; // both names MUST be valid JavaScript identifiers if (JsScanner.IsValidIdentifier(sourceName) && JsScanner.IsValidIdentifier(newName)) { if (m_identifierReplacementMap.ContainsKey(sourceName)) { // just replace the value m_identifierReplacementMap[sourceName] = newName; } else { // add the new pair m_identifierReplacementMap.Add(sourceName, newName); } // if we get here, we added it (or updated it if it's a dupe) successfullyAdded = true; } return(successfullyAdded); }
private static void ResolveLookup(JsActivationObject scope, JsLookup lookup, JsSettings settings) { // resolve lookup via the lexical scope lookup.VariableField = scope.FindReference(lookup.Name); if (lookup.VariableField.FieldType == JsFieldType.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 JsUnaryOperator; 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 JsCallNode; var isFunction = callNode != null && callNode.Function == lookup; lookup.Context.HandleError((isFunction ? JsError.UndeclaredFunction : JsError.UndeclaredVariable), false); } } } else if (lookup.VariableField.FieldType == JsFieldType.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 JsMember; 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 JsCallNode; if (callNode != null && callNode.InBrackets && callNode.Arguments.Count == 1 && callNode.Arguments[0] is JsConstantWrapper && callNode.Arguments[0].FindPrimitiveType() == JsPrimitiveType.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 != JsEvalTreatment.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 JsCallNode; 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; }