;
108 |
109 | if (objItemDictionary != null)
110 | {
111 | processor(objItemDictionary);
112 | }
113 | }
114 | }
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/source/SharpLinter/SharpLinter.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 8.0.30703
7 | 2.0
8 | {63E74178-8088-4EFE-85BB-5A3F27651D75}
9 | Library
10 | Properties
11 | JTC.SharpLinter
12 | JTC.SharpLinter
13 | v4.0
14 | 512
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 | False
36 | ..\..\dist\EcmaScript.NET.modified.dll
37 |
38 |
39 | False
40 | ..\..\dist\Noesis.Javascript.dll
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | False
51 | ..\..\dist\Yahoo.Yui.Compressor.dll
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
94 |
--------------------------------------------------------------------------------
/source/SharpLinterExe/SharpLinterExe.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 9.0.30729
7 | 2.0
8 | {7EB49A89-EB46-44E3-86EF-18CA98C93668}
9 | Exe
10 | Properties
11 | JTC.SharpLinter
12 | SharpLinter
13 | v4.0
14 | 512
15 |
16 |
17 | 3.5
18 |
19 |
20 | publish\
21 | true
22 | Disk
23 | false
24 | Foreground
25 | 7
26 | Days
27 | false
28 | false
29 | true
30 | 0
31 | 1.0.0.%2a
32 | false
33 | false
34 | true
35 |
36 |
37 | true
38 | full
39 | false
40 | bin\Debug\
41 | DEBUG;TRACE
42 | prompt
43 | 4
44 | AllRules.ruleset
45 | x86
46 |
47 |
48 | pdbonly
49 | true
50 | bin\Release\
51 | TRACE
52 | prompt
53 | 4
54 | AllRules.ruleset
55 | x86
56 |
57 |
58 |
59 |
60 | 3.5
61 |
62 |
63 | 3.5
64 |
65 |
66 | 3.5
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | False
81 | .NET Framework 3.5 SP1 Client Profile
82 | false
83 |
84 |
85 | False
86 | .NET Framework 3.5 SP1
87 | true
88 |
89 |
90 | False
91 | Windows Installer 3.1
92 | true
93 |
94 |
95 |
96 |
97 | {63E74178-8088-4EFE-85BB-5A3F27651D75}
98 | SharpLinter
99 |
100 |
101 |
102 |
109 |
--------------------------------------------------------------------------------
/source/SharpLinter.Test/resources/noerrors.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IQTest Javascript Testing Framework Test Runner
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
161 |
162 | Test Test Page Run Tests
163 |
164 |
165 |
166 |
167 |
168 |
169 | Hello!!
170 |
171 |
172 |
--------------------------------------------------------------------------------
/source/SharpLinter/SharpCompressor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using Yahoo.Yui.Compressor;
6 | using System.IO;
7 |
8 | namespace JTC.SharpLinter
9 | {
10 | public enum CompressorType
11 | {
12 | yui = 1,
13 | packer = 2,
14 | best = 3
15 | }
16 | public class SharpCompressor
17 | {
18 | public SharpCompressor()
19 | {
20 | CompressorType = CompressorType.best;
21 | }
22 | public CompressorType CompressorType {get;set;}
23 | public bool AllowEval
24 | { get; set; }
25 | public string Input
26 | { get; set; }
27 | protected LinterECMAErrorReporter Reporter = new LinterECMAErrorReporter();
28 |
29 | public bool YUITest(string javascript)
30 | {
31 |
32 | Clear();
33 | string compressed;
34 | return compressYUI(javascript, out compressed);
35 | }
36 | public void Clear()
37 | {
38 | Reporter.Clear();
39 | //CompressorType = CompressorType.best;
40 | Input = null;
41 | Statistics = null;
42 | //Input = null;
43 | Output = null;
44 | KeepHeader = false;
45 | }
46 | protected List _Errors = new List();
47 | public bool Success
48 | {
49 | get {
50 | return Reporter.Errors.Count==0;
51 | }
52 | }
53 | public int ErrorCount
54 | {
55 | get
56 | {
57 | return _Errors.Count;
58 | }
59 | }
60 | public IEnumerable Errors
61 | {
62 | get
63 | {
64 | return Reporter.Errors;
65 | }
66 | }
67 | public string Statistics
68 | {
69 | get;
70 | protected set;
71 | }
72 | //public string Input { get; set; }
73 | public string Output { get; protected set; }
74 | public bool KeepHeader { get; set; }
75 |
76 | public bool Minimize()
77 | {
78 | if (String.IsNullOrEmpty(Input)) {
79 | throw new Exception("Input must be specified.:");
80 | }
81 | if (CompressorType == 0)
82 | {
83 | throw new Exception("No compressor type specified.");
84 | }
85 | string compressedYui = String.Empty;
86 | string compressedPacker = String.Empty;
87 | string header = String.Empty;
88 | string javascript = Input;
89 | //string javascript = File.ReadAllText(path);
90 | if (KeepHeader)
91 | {
92 | int pos = javascript.IndexOf("/*");
93 | int endPos = -1;
94 | string leadin = String.Empty;
95 | if (pos >= 0)
96 | {
97 | leadin = javascript.Substring(0, pos);
98 | if (leadin.Trim() == string.Empty)
99 | {
100 | endPos = javascript.IndexOf("*/", pos + 1);
101 | header = javascript.Substring(pos, endPos + 2) + Environment.NewLine;
102 | javascript = javascript.Substring(endPos + 2);
103 |
104 | }
105 | }
106 | }
107 |
108 | if (CompressorType == CompressorType.yui || CompressorType == CompressorType.best)
109 | {
110 | if (!compressYUI(javascript, out compressedYui)) {
111 |
112 | throw new Exception("The YUI compressor reported errors, which is strange because we already did a dry run to determine the best compressor.");
113 | }
114 | }
115 | if (CompressorType == CompressorType.packer || CompressorType == CompressorType.best)
116 | {
117 | JavascriptPacker jsPacker = new JavascriptPacker(JavascriptPacker.PackerEncoding.None, false, false);
118 | compressedPacker = jsPacker.Pack(javascript);
119 | }
120 | CompressorType finalPacker = CompressorType != CompressorType.best ? CompressorType :
121 | (compressedYui.Length < compressedPacker.Length ? CompressorType.yui : CompressorType.packer);
122 |
123 | Output = header + (finalPacker == CompressorType.yui ? compressedYui : compressedPacker);
124 |
125 | Statistics = finalPacker.ToString() + ": " + javascript.Length + "/" + Output.Length + ", "
126 | + Math.Round(100 * ((decimal)Output.Length / (decimal)javascript.Length), 0) + "%";
127 | return Success;
128 | }
129 |
130 | protected bool compressYUI(string input, out string output)
131 | {
132 | bool success;
133 | try
134 | {
135 | var reporter = new LinterECMAErrorReporter();
136 | Yahoo.Yui.Compressor.JavaScriptCompressor compressor;
137 | compressor = new JavaScriptCompressor(input, true, Encoding.UTF8,
138 | System.Globalization.CultureInfo.CurrentCulture,
139 | AllowEval,
140 | reporter);
141 | output = compressor.Compress(true, true,false, 80);
142 | success = Reporter.Errors.Count == 0;
143 | }
144 | catch
145 | {
146 | output = "";
147 | success = false;
148 | }
149 |
150 | return success;
151 | }
152 |
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/source/SharpLinter/Config/FilePathMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 |
7 | namespace JTC.SharpLinter.Config
8 | {
9 | public static class FilePathMatcher
10 | {
11 | ///
12 | /// Returns only the files from names that match pattern, unless exclude, in which case only those that don't match
13 | ///
14 | ///
15 | ///
16 | ///
17 | public static IEnumerable MatchFiles(string pattern, IEnumerable names, bool exclude)
18 | {
19 | string[] patterns = new string[] {pattern};
20 | return MatchFiles(patterns, names, exclude);
21 |
22 | }
23 | public static IEnumerable MatchFiles(IEnumerable patterns, IEnumerable names, bool exclude)
24 | {
25 | List matches = new List();
26 | Regex nameRegex=null;
27 | bool match=false;
28 | // bool noPatterns = true;
29 |
30 | foreach (string path in names)
31 | {
32 | string cleanPath = path.Replace("/", "\\");
33 | string fileNameOnly = FileNamePart(path);
34 | foreach (string pattern in patterns)
35 | {
36 | //noPatterns = false;
37 | string cleanPattern = pattern.Replace("/", "\\");
38 | string namePattern = FileNamePart(cleanPattern);
39 | string pathPattern = PathPart(cleanPattern);
40 |
41 | if (namePattern != String.Empty)
42 | {
43 | nameRegex = FindFilesPatternToRegex.Convert(namePattern);
44 | }
45 |
46 |
47 | match = (pathPattern == String.Empty ? true : MatchPathOnly(cleanPattern, cleanPath)) && (namePattern == String.Empty ? true : nameRegex.IsMatch(fileNameOnly));
48 | if (match)
49 | {
50 | break;
51 | }
52 | }
53 | if (match != exclude)
54 | {
55 | yield return path;
56 | }
57 | }
58 | // think this was from when we had the patterns/paths loops inverted. Should never be needed (actually causes dups now)
59 | // .. if the pattern loop is skipped, match will be false, as is correct.
60 |
61 | //if (noPatterns)
62 | //{
63 | // foreach (string name in names)
64 | // {
65 | // yield return name;
66 | // }
67 | //}
68 | }
69 | private static string FileNamePart(string pattern)
70 | {
71 | return pattern.Substring(pattern.Length - 1, 1) == "\\" ? String.Empty :
72 | (pattern.IndexOf("\\") == -1 ? pattern : pattern.AfterLast("\\"));
73 | }
74 | private static string PathPart(string pattern)
75 | {
76 | return pattern.Substring(pattern.Length - 1, 1) == "\\" ? pattern :
77 | (pattern.IndexOf("\\") == -1 ? string.Empty : pattern.BeforeLast("\\"));
78 | }
79 |
80 | private static bool MatchPathOnly(string pattern, string path)
81 | {
82 | if (pattern.Substring(pattern.Length - 1, 1) != "\\")
83 | {
84 | return false;
85 | }
86 | if (pattern.IndexOf(":") > 0 || pattern.IndexOf("\\\\") == 0)
87 | {
88 | return path.StartsWith(pattern);
89 | }
90 | else
91 | {
92 | return path.IndexOf(pattern) >= 0;
93 | }
94 | }
95 |
96 | private static class FindFilesPatternToRegex
97 | {
98 | private static Regex HasQuestionMarkRegEx = new Regex(@"\?", RegexOptions.Compiled);
99 | private static Regex HasAsteriskRegex = new Regex(@"\*", RegexOptions.Compiled);
100 | private static Regex IlegalCharactersRegex = new Regex("[" + @"\/:<>|" + "\"]", RegexOptions.Compiled);
101 | private static Regex CatchExtentionRegex = new Regex(@"^\s*.+\.([^\.]+)\s*$", RegexOptions.Compiled);
102 | private static string NonDotCharacters = @"[^.]*";
103 | public static Regex Convert(string pattern)
104 | {
105 | if (pattern == null)
106 | {
107 | throw new ArgumentNullException();
108 | }
109 | pattern = pattern.Trim();
110 | if (pattern.Length == 0)
111 | {
112 | throw new ArgumentException("Pattern is empty.");
113 | }
114 | if(IlegalCharactersRegex.IsMatch(pattern))
115 | {
116 | throw new ArgumentException("Patterns contains illegal characters.");
117 | }
118 | bool hasExtension = CatchExtentionRegex.IsMatch(pattern);
119 | bool matchExact = false;
120 | if (HasQuestionMarkRegEx.IsMatch(pattern))
121 | {
122 | matchExact = true;
123 | }
124 | else if(hasExtension)
125 | {
126 | matchExact = CatchExtentionRegex.Match(pattern).Groups[1].Length != 3;
127 | }
128 | string regexString = Regex.Escape(pattern);
129 | regexString = "^" + Regex.Replace(regexString, @"\\\*", ".*");
130 | regexString = Regex.Replace(regexString, @"\\\?", ".");
131 | if(!matchExact && hasExtension)
132 | {
133 | regexString += NonDotCharacters;
134 | }
135 | regexString += "$";
136 | Regex regex = new Regex(regexString, RegexOptions.Compiled | RegexOptions.IgnoreCase);
137 | return regex;
138 | }
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/source/SharpLinter/Utility.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.IO;
6 |
7 | namespace JTC.SharpLinter
8 | {
9 | public static class ExtensionMethods
10 | {
11 | ///
12 | /// Returns the text between startIndex and endIndex (exclusive of endIndex)
13 | ///
14 | ///
15 | ///
16 | ///
17 | ///
18 | public static string SubstringBetween(this string text, int startIndex, int endIndex)
19 | {
20 | return (text.Substring(startIndex, endIndex - startIndex));
21 | }
22 | public static string AfterLast(this string text, string find)
23 | {
24 | int index = text.LastIndexOf(find);
25 | if (index < 0 || index + find.Length >= text.Length)
26 | {
27 | return (String.Empty);
28 | }
29 | else
30 | {
31 | return (text.Substring(index + find.Length));
32 | }
33 | }
34 | ///
35 | /// Removes /r /n /t and spaces
36 | ///
37 | ///
38 | ///
39 | public static string RemoveWhitespace(this string text)
40 | {
41 | int pos=-1;
42 | int len=text.Length;
43 | HashSet removeList = new HashSet("\r\n\t ");
44 | StringBuilder output = new StringBuilder();
45 | while (++pos < len)
46 | {
47 | if (!removeList.Contains(text[pos]))
48 | {
49 | output.Append(text[pos]);
50 | }
51 |
52 | }
53 | return output.ToString();
54 | }
55 | }
56 | public static class Utility
57 | {
58 | ///
59 | /// Resolves a path, using the application root as the path for nonrooted files
60 | ///
61 | ///
62 | public static string ResolveRelativePath_AppRoot(string path)
63 | {
64 | return Path.IsPathRooted(path) ?
65 | path:
66 | System.Reflection.Assembly.GetExecutingAssembly().Location.BeforeLast("\\") + "\\" + path;
67 |
68 | }
69 | ///
70 | /// Qualifies a relative path file, using the current directory for non rooted files
71 | ///
72 | ///
73 | ///
74 | public static string ResolveRelativePath_Current(string path)
75 | {
76 | return Path.IsPathRooted(path) ?
77 | path:
78 | Directory.GetCurrentDirectory() + "\\" + path;
79 | }
80 | ///
81 | /// Evaluates the string to determine whether the value is true or false. Valid true strings are any form of "yes," "true," "on," or the digit "1"
82 | /// False is always returned otherwise.
83 | ///
84 | ///
85 | ///
86 | public static bool IsTrueString(string theString)
87 | {
88 | return ((bool)StringToBool(theString, false));
89 | }
90 |
91 | ///
92 | /// Like IsTrueString, but if a true or false value is not matched, the default value is returned. The default can be null if no known
93 | /// true/false strings are matched.
94 | ///
95 | ///
96 | ///
97 | ///
98 | public static bool? StringToBool(string theString, bool? defaultValue)
99 | {
100 | string lcaseString = (theString == null ? "" : theString.ToLower().Trim());
101 | if (lcaseString == "true" || lcaseString == "yes" || lcaseString == "y" || lcaseString == "1" || lcaseString == "on")
102 | {
103 | return (true);
104 | }
105 | else if (lcaseString == "false" || lcaseString == "no" || lcaseString == "n" || lcaseString == "0" || lcaseString == "off")
106 | {
107 | return (false);
108 | }
109 | else
110 | {
111 | return (defaultValue);
112 | }
113 |
114 | }
115 | public static string BeforeIncluding(this string text, string find)
116 | {
117 | int index = text.IndexOf(find);
118 | if (index < 0 || index == text.Length)
119 | {
120 | return (String.Empty);
121 | }
122 | else
123 | {
124 | return (text.Substring(0, index+find.Length));
125 | }
126 | }
127 | public static string Before(this string text, string find)
128 | {
129 | int index = text.IndexOf(find);
130 | if (index < 0 || index == text.Length)
131 | {
132 | return (String.Empty);
133 | }
134 | else
135 | {
136 | return (text.Substring(0, index));
137 | }
138 | }
139 | public static string BeforeLast(this string text, string find)
140 | {
141 | int index = text.LastIndexOf(find);
142 | if (index >= 0)
143 | {
144 | return (text.Substring(0, index));
145 | }
146 | else
147 | {
148 | return String.Empty;
149 | }
150 | }
151 |
152 | public static int OccurrencesOf(this string text, string find)
153 | {
154 | bool finished = false;
155 | int pos=0;
156 | int occurrences=0;
157 | while (!finished)
158 | {
159 | pos = text.IndexOf(find,pos);
160 | if (pos >= 0)
161 | {
162 | occurrences++;
163 | pos++;
164 | if (pos == text.Length)
165 | {
166 | finished = true;
167 | }
168 | }
169 | else
170 | {
171 | finished = true;
172 | }
173 |
174 | }
175 | return occurrences;
176 |
177 | }
178 | public static string AddListItem(this string list, string value, string separator)
179 | {
180 | if (String.IsNullOrEmpty(value))
181 | {
182 | return list.Trim();
183 | }
184 | if (list == null)
185 | {
186 | list = String.Empty;
187 | }
188 | else
189 | {
190 | list = list.Trim();
191 | }
192 |
193 | int pos = (list + separator).IndexOf(value + separator);
194 | if (pos < 0)
195 | {
196 | if (list.LastIndexOf(separator) == list.Length - separator.Length)
197 | {
198 | // do not add separator - it already exists
199 | return list + value;
200 | }
201 | else
202 | {
203 | return (list + (list == "" ? "" : separator) + value);
204 | }
205 | }
206 | else
207 | {
208 | // already has value
209 | return (list);
210 | }
211 | }
212 | }
213 |
214 | }
215 |
--------------------------------------------------------------------------------
/source/SharpLinter/JSPackerParser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using System.Text.RegularExpressions;
4 | using System.Collections;
5 | using System.Collections.Specialized;
6 |
7 | /*
8 | ParseMaster, version 1.0 (pre-release) (2005/02/01) x4
9 | Copyright 2005, Dean Edwards
10 | Web: http://dean.edwards.name/
11 |
12 | This software is licensed under the CC-GNU LGPL
13 | Web: http://creativecommons.org/licenses/LGPL/2.1/
14 |
15 | Ported to C# by Jesse Hansen, twindagger2k@msn.com
16 | */
17 |
18 | namespace JTC.SharpLinter
19 | {
20 | ///
21 | /// a multi-pattern parser
22 | ///
23 | internal class ParseMaster
24 | {
25 | // used to determine nesting levels
26 | Regex GROUPS = new Regex("\\("),
27 | SUB_REPLACE = new Regex("\\$"),
28 | INDEXED = new Regex("^\\$\\d+$"),
29 | ESCAPE = new Regex("\\\\."),
30 | QUOTE = new Regex("'"),
31 | DELETED = new Regex("\\x01[^\\x01]*\\x01");
32 |
33 | ///
34 | /// Delegate to call when a regular expression is found.
35 | /// Use match.Groups[offset + <group number>].Value to get
36 | /// the correct subexpression
37 | ///
38 | public delegate string MatchGroupEvaluator(Match match, int offset);
39 |
40 | private string DELETE(Match match, int offset)
41 | {
42 | return "\x01" + match.Groups[offset].Value + "\x01";
43 | }
44 |
45 | private bool ignoreCase = false;
46 | private char escapeChar = '\0';
47 |
48 | ///
49 | /// Ignore Case?
50 | ///
51 | public bool IgnoreCase
52 | {
53 | get { return ignoreCase; }
54 | set { ignoreCase = value; }
55 | }
56 |
57 | ///
58 | /// Escape Character to use
59 | ///
60 | public char EscapeChar
61 | {
62 | get { return escapeChar; }
63 | set { escapeChar = value; }
64 | }
65 |
66 | ///
67 | /// Add an expression to be deleted
68 | ///
69 | /// Regular Expression String
70 | public void Add(string expression)
71 | {
72 | Add(expression, string.Empty);
73 | }
74 |
75 | ///
76 | /// Add an expression to be replaced with the replacement string
77 | ///
78 | /// Regular Expression String
79 | /// Replacement String. Use $1, $2, etc. for groups
80 | public void Add(string expression, string replacement)
81 | {
82 | if (replacement == string.Empty)
83 | add(expression, new MatchGroupEvaluator(DELETE));
84 |
85 | add(expression, replacement);
86 | }
87 |
88 | ///
89 | /// Add an expression to be replaced using a callback function
90 | ///
91 | /// Regular expression string
92 | /// Callback function
93 | public void Add(string expression, MatchGroupEvaluator replacement)
94 | {
95 | add(expression, replacement);
96 | }
97 |
98 | ///
99 | /// Executes the parser
100 | ///
101 | /// input string
102 | /// parsed string
103 | public string Exec(string input)
104 | {
105 | return DELETED.Replace(unescape(getPatterns().Replace(escape(input), new MatchEvaluator(replacement))), string.Empty);
106 | //long way for debugging
107 | /*input = escape(input);
108 | Regex patterns = getPatterns();
109 | input = patterns.Replace(input, new MatchEvaluator(replacement));
110 | input = DELETED.Replace(input, string.Empty);
111 | return input;*/
112 | }
113 |
114 | ArrayList patterns = new ArrayList();
115 | private void add(string expression, object replacement)
116 | {
117 | Pattern pattern = new Pattern();
118 | pattern.expression = expression;
119 | pattern.replacement = replacement;
120 | //count the number of sub-expressions
121 | // - add 1 because each group is itself a sub-expression
122 | pattern.length = GROUPS.Matches(internalEscape(expression)).Count + 1;
123 |
124 | //does the pattern deal with sup-expressions?
125 | if (replacement is string && SUB_REPLACE.IsMatch((string)replacement))
126 | {
127 | string sreplacement = (string)replacement;
128 | // a simple lookup (e.g. $2)
129 | if (INDEXED.IsMatch(sreplacement))
130 | {
131 | pattern.replacement = int.Parse(sreplacement.Substring(1)) - 1;
132 | }
133 | }
134 |
135 | patterns.Add(pattern);
136 | }
137 |
138 | ///
139 | /// builds the patterns into a single regular expression
140 | ///
141 | ///
142 | private Regex getPatterns()
143 | {
144 | StringBuilder rtrn = new StringBuilder(string.Empty);
145 | foreach (object pattern in patterns)
146 | {
147 | rtrn.Append(((Pattern)pattern).ToString() + "|");
148 | }
149 | rtrn.Remove(rtrn.Length - 1, 1);
150 | return new Regex(rtrn.ToString(), ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None);
151 | }
152 |
153 | ///
154 | /// Global replacement function. Called once for each match found
155 | ///
156 | /// Match found
157 | private string replacement(Match match)
158 | {
159 | int i = 1, j = 0;
160 | Pattern pattern;
161 | //loop through the patterns
162 | while (!((pattern = (Pattern)patterns[j++]) == null))
163 | {
164 | //do we have a result?
165 | if (match.Groups[i].Value != string.Empty)
166 | {
167 | object replacement = pattern.replacement;
168 | if (replacement is MatchGroupEvaluator)
169 | {
170 | return ((MatchGroupEvaluator)replacement)(match, i);
171 | }
172 | else if (replacement is int)
173 | {
174 | return match.Groups[(int)replacement + i].Value;
175 | }
176 | else
177 | {
178 | //string, send to interpreter
179 | return replacementString(match, i, (string)replacement, pattern.length);
180 | }
181 | }
182 | else //skip over references to sub-expressions
183 | i += pattern.length;
184 | }
185 | return match.Value; //should never be hit, but you never know
186 | }
187 |
188 | ///
189 | /// Replacement function for complicated lookups (e.g. Hello $3 $2)
190 | ///
191 | private string replacementString(Match match, int offset, string replacement, int length)
192 | {
193 | while (length > 0)
194 | {
195 | replacement = replacement.Replace("$" + length--, match.Groups[offset + length].Value);
196 | }
197 | return replacement;
198 | }
199 |
200 | private StringCollection escaped = new StringCollection();
201 |
202 | //encode escaped characters
203 | private string escape(string str)
204 | {
205 | if (escapeChar == '\0')
206 | return str;
207 | Regex escaping = new Regex("\\\\(.)");
208 | return escaping.Replace(str, new MatchEvaluator(escapeMatch));
209 | }
210 |
211 | private string escapeMatch(Match match)
212 | {
213 | escaped.Add(match.Groups[1].Value);
214 | return "\\";
215 | }
216 |
217 | //decode escaped characters
218 | private int unescapeIndex = 0;
219 | private string unescape(string str)
220 | {
221 | if (escapeChar == '\0')
222 | return str;
223 | Regex unescaping = new Regex("\\" + escapeChar);
224 | return unescaping.Replace(str, new MatchEvaluator(unescapeMatch));
225 | }
226 |
227 | private string unescapeMatch(Match match)
228 | {
229 | return "\\" + escaped[unescapeIndex++];
230 | }
231 |
232 | private string internalEscape(string str)
233 | {
234 | return ESCAPE.Replace(str, "");
235 | }
236 |
237 | //subclass for each pattern
238 | private class Pattern
239 | {
240 | public string expression;
241 | public object replacement;
242 | public int length;
243 |
244 | public override string ToString()
245 | {
246 | return "(" + expression + ")";
247 | }
248 | }
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/source/SharpLinter/SharpLinterBatch.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.IO;
6 |
7 | namespace JTC.SharpLinter.Config
8 | {
9 | public class SharpLinterBatch
10 | {
11 | public JsLintConfiguration Configuration
12 | {
13 | get;
14 | set;
15 | }
16 | public string OutputFormat
17 | {
18 | get
19 | {
20 | if (HasCustomOutputFormat())
21 | {
22 | return Configuration.OutputFormat;
23 | }
24 | else
25 | {
26 | return _OutputFormat;
27 | }
28 | }
29 | set
30 | {
31 | _OutputFormat = value;
32 | }
33 | }
34 |
35 | private string _OutputFormat;
36 | public IEnumerable FilePaths { get; set; }
37 |
38 | public SharpLinterBatch(JsLintConfiguration configuration)
39 | {
40 | Configuration = configuration;
41 | OutputFormat = "{0}({1}): ({2}) {3} {4}";
42 | }
43 |
44 | protected string StringOrMissingDescription(string text)
45 | {
46 | return String.IsNullOrEmpty(text) ? "[None Specified]" : text;
47 |
48 | }
49 |
50 | public int Process()
51 | {
52 | SharpLinter lint = new SharpLinter(Configuration);
53 | List SummaryInfo = new List();
54 |
55 | if (Configuration.Verbosity == Verbosity.Debugging)
56 | {
57 | Console.WriteLine(String.Format("SharpLinter: Beginning processing at {0:MM/dd/yy H:mm:ss zzz}", DateTime.Now));
58 | Console.WriteLine(String.Format("Global configuration file: {0}",StringOrMissingDescription(Configuration.GlobalConfigFilePath)));
59 | Console.WriteLine(String.Format("JSLINT path: {0}", StringOrMissingDescription(Configuration.JsLintFilePath)));
60 | Console.WriteLine(String.Format("Using linter options for {0}, {1}",Configuration.LinterType.ToString(),StringOrMissingDescription(Configuration.JsLintVersion)));
61 | Console.WriteLine("LINT options: " + StringOrMissingDescription(Configuration.OptionsToString()));
62 | Console.WriteLine("LINT globals: " + StringOrMissingDescription(Configuration.GlobalsToString()));
63 | Console.WriteLine("Sharplint: ignorestart={0}, ignoreend={1}, ignorefile={2}",Configuration.IgnoreStart,Configuration.IgnoreEnd,Configuration.IgnoreFile);
64 | Console.WriteLine("Input paths: (working directory=" + Directory.GetCurrentDirectory()+")");
65 | foreach (var file in FilePaths)
66 | {
67 | Console.WriteLine(" " + file.Path);
68 | }
69 | Console.WriteLine("Exclude file masks:");
70 | foreach (var file in Configuration.ExcludeFiles)
71 | {
72 | Console.WriteLine(" " + file);
73 | }
74 | Console.WriteLine("----------------------------------------");
75 | }
76 | int fileCount = 0;
77 |
78 | List allErrors = new List();
79 |
80 |
81 | foreach (string file in Configuration.GetMatchedFiles(FilePaths))
82 | {
83 | List fileErrors = new List();
84 | bool lintErrors=false;
85 | bool YUIErrors=false;
86 | fileCount++;
87 | string javascript = File.ReadAllText(file);
88 | if (javascript.IndexOf("/*" + Configuration.IgnoreFile + "*/") >= 0)
89 | {
90 | continue;
91 | }
92 |
93 | var ext = Path.GetExtension(file).ToLower();
94 | Configuration.InputType = (ext == ".js" || ext == ".javascript") ?
95 | InputType.JavaScript :
96 | InputType.Html;
97 |
98 | JsLintResult result = lint.Lint(javascript);
99 | bool hasErrors = result.Errors.Count > 0;
100 |
101 | if (hasErrors)
102 | {
103 | lintErrors = true;
104 | foreach (JsLintData error in result.Errors)
105 | {
106 | error.FilePath = file;
107 | fileErrors.Add(error);
108 | }
109 | string leadIn = String.Format("{0}: Lint found {1} errors.", file, result.Errors.Count);
110 |
111 |
112 | if (result.Limited) {
113 | leadIn += String.Format(" Stopped processing due to maxerr={0} option.", Configuration.MaxErrors);
114 | }
115 | SummaryInfo.Add(leadIn);
116 | }
117 |
118 | SharpCompressor compressor = new SharpCompressor();
119 |
120 | // We always check for YUI errors when there were no lint errors and
121 | // we are compressing. Otherwise it might not compress.
122 |
123 | if (Configuration.YUIValidation ||
124 | (!hasErrors && Configuration.MinimizeOnSuccess))
125 | {
126 | compressor.Clear();
127 | compressor.AllowEval = Configuration.GetOption("evil");
128 | compressor.KeepHeader = Configuration.MinimizeKeepHeader;
129 | compressor.CompressorType = Configuration.CompressorType;
130 |
131 | hasErrors = !compressor.YUITest(javascript);
132 |
133 | if (hasErrors)
134 | {
135 | YUIErrors = true;
136 | foreach (var error in compressor.Errors)
137 | {
138 | fileErrors.Add(error);
139 | }
140 |
141 | SummaryInfo.Add(String.Format("{0}: YUI compressor found {1} errors.", file, compressor.ErrorCount));
142 | }
143 | }
144 |
145 | string successLine = String.Empty;
146 | if (!(lintErrors || YUIErrors))
147 | {
148 | successLine = String.Format("{0}: No errors found.", file);
149 |
150 | if (Configuration.MinimizeOnSuccess)
151 | {
152 | compressor.Clear();
153 | compressor.Input = javascript;
154 | compressor.CompressorType = Configuration.CompressorType;
155 | compressor.KeepHeader = Configuration.MinimizeKeepHeader;
156 |
157 | string target = MapFileName(file, Configuration.MinimizeFilenameMask);
158 | try
159 | {
160 | //Delete no matter what - there should never be a mismatch between regular & min
161 | if (File.Exists(target))
162 | {
163 | File.Delete(target);
164 | }
165 |
166 | if (compressor.Minimize())
167 | {
168 | File.WriteAllText(target, compressor.Output);
169 | string path = target.BeforeLast("\\");
170 | if (target.StartsWith(path))
171 | {
172 | path = "." + target.Substring(path.Length);
173 | }
174 | else
175 | {
176 | path = file;
177 | }
178 | successLine = successLine.AddListItem(String.Format("Compressed to '{0}' ({1})", path, compressor.Statistics), " ");
179 | }
180 | else
181 | {
182 | successLine = successLine.AddListItem("Errors were reported by the compressor. It's weird, but try running YUI validation."," ");
183 | }
184 | }
185 |
186 | catch (Exception e)
187 | {
188 | successLine=successLine.AddListItem(String.Format("Unable to compress output to '{0}': {1}", target, e.Message)," ");
189 | }
190 | }
191 | SummaryInfo.Add(successLine);
192 | }
193 | fileErrors.Sort(LintDataComparer);
194 | allErrors.AddRange(fileErrors);
195 | }
196 | if (Configuration.Verbosity == Verbosity.Debugging || Configuration.Verbosity == Verbosity.Summary)
197 | {
198 | // Output file-by-file results at beginning
199 | foreach (string item in SummaryInfo)
200 | {
201 | Console.WriteLine(item);
202 | }
203 | }
204 |
205 |
206 | if (allErrors.Count > 0)
207 | {
208 | if (Configuration.Verbosity == Verbosity.Debugging) {
209 | Console.WriteLine();
210 | Console.WriteLine("Error Details:");
211 | Console.WriteLine();
212 | }
213 |
214 | foreach (JsLintData error in allErrors)
215 | {
216 | string character = error.Character.ToString();
217 | // add a small introducing string before the character number if we are using the default output string
218 | if (!HasCustomOutputFormat())
219 | {
220 | character = error.Character >= 0 ? "at character " + error.Character : String.Empty;
221 | }
222 | Console.WriteLine(string.Format(OutputFormat, error.FilePath, error.Line, error.Source, error.Reason, character));
223 | }
224 | }
225 | if (Configuration.Verbosity == Verbosity.Debugging)
226 | {
227 | Console.WriteLine();
228 | Console.WriteLine("SharpLinter: Finished processing at {0:MM/dd/yy H:mm:ss zzz}. Processed {1} files.", DateTime.Now, fileCount);
229 | }
230 |
231 | return allErrors.Count;
232 | }
233 |
234 | private string MapFileName(string path, string mask)
235 | {
236 | if (mask.OccurrencesOf("*") != 1)
237 | {
238 | throw new Exception("Invalid mask '" + mask + "' for compressing output. It must have a single wildcard.");
239 | }
240 |
241 | string maskStart = mask.Before("*");
242 | string maskEnd = mask.AfterLast("*").BeforeLast(".");
243 | string maskExt = mask.AfterLast(".");
244 |
245 | string pathBase = path.BeforeLast(".");
246 | return maskStart + pathBase + maskEnd + "." + maskExt;
247 |
248 | }
249 |
250 | private int LintDataComparer(JsLintData x, JsLintData y)
251 | {
252 | return x.Line.CompareTo(y.Line);
253 | }
254 |
255 | private bool HasCustomOutputFormat()
256 | {
257 | return !String.IsNullOrEmpty(Configuration.OutputFormat);
258 | }
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/source/SharpLinter/SharpLinter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Collections.Specialized;
5 | using System.Linq;
6 | using System.Text;
7 | using System.IO;
8 | using System.Reflection;
9 | using System.Text.RegularExpressions;
10 | using Noesis.Javascript;
11 | using JTC.SharpLinter.Config;
12 |
13 |
14 | namespace JTC.SharpLinter
15 | {
16 | ///
17 | /// Constructs an object capable of linting javascript files and returning the result of JS Lint
18 | ///
19 | public class SharpLinter
20 | {
21 | #region constructor
22 |
23 | public SharpLinter(JsLintConfiguration config)
24 | {
25 | Configuration = config;
26 | Initialize();
27 | }
28 |
29 | protected void Initialize()
30 | {
31 | _context = new Engines.Neosis();
32 | //_context = new JavascriptContext();
33 |
34 | if (String.IsNullOrEmpty(Configuration.JsLintCode))
35 | {
36 | throw new Exception("No JSLINT/JSHINT code was specified in the configuration.");
37 | }
38 | else
39 | {
40 | JSLint = Configuration.JsLintCode;
41 | }
42 |
43 | _context.Run(JSLint);
44 |
45 | string func = Configuration.LinterType == LinterType.JSHint ? "JSHINT" : "JSLINT";
46 |
47 | // a bug (apparently) in the Noesis wrapper causes a StackOverflow exception when returning data sometimes.
48 | // not sure why but removing "functions" from the returned object resolves it. we don't need that
49 | // anyway.
50 |
51 | string run =
52 | @"lintRunner = function (dataCollector, javascript, options) {
53 | var data, result = JSLINT(javascript,options);
54 |
55 | if (!result) {
56 | data = JSLINT.data();
57 | if (data.functions) {
58 | delete data.functions;
59 | }
60 | dataCollector.ProcessData(data);
61 | }
62 | };
63 | ".Replace("JSLINT", func);
64 | _context.Run(run);
65 | }
66 |
67 | #endregion
68 |
69 | #region private methods
70 |
71 | private IJSEngineWrapper _context;
72 | private object _lock = new Object();
73 |
74 | ///
75 | /// Map of lines that should be excluded (true for an index means exclude that line)
76 | ///
77 | protected List LineExclusion;
78 |
79 | ///
80 | /// The script that gets run
81 | ///
82 | protected string JSLint;
83 |
84 | protected JsLintConfiguration Configuration;
85 |
86 |
87 | #endregion
88 |
89 | #region public methods
90 |
91 | protected void Configure()
92 | {
93 | _isStartScriptRegex = new Regex(@"