Skip to content

Commit 8eb7b5d

Browse files
committed
Repair character positions without touching the configuration
JSHint reports physical character positions instead of a character index, i.e. all tab characters are multiplied with the (configurable) indent. We used to set the option "indent" to 1 to get the real character index. But this setting could be overwritten in a custom configuration. Now we leave the configuration untouched and repair the character position based on the configured or default indent.
1 parent a130217 commit 8eb7b5d

File tree

9 files changed

+102
-30
lines changed

9 files changed

+102
-30
lines changed

com.eclipsesource.jshint.test/src/com/eclipsesource/jshint/Configuration_Test.java

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,80 +27,75 @@ public void setUp() {
2727

2828
@Test
2929
public void emptyAfterCreation() {
30-
assertEquals( "{\"indent\": 1}", configuration.toJson() );
30+
assertEquals( "{}", configuration.toJson() );
3131
}
3232

3333
@Test
3434
public void addOneOption() {
3535
configuration.addOption( "foo", true );
3636

37-
assertEquals( "{\"indent\": 1, \"foo\": true}", configuration.toJson() );
37+
assertEquals( "{\"foo\": true}", configuration.toJson() );
3838
}
3939

4040
@Test
4141
public void addSeparateOptions() {
4242
configuration.addOption( "foo", true );
4343
configuration.addOption( "bar", false );
4444

45-
assertEquals( "{\"indent\": 1, \"foo\": true, \"bar\": false}",
46-
configuration.toJson() );
45+
assertEquals( "{\"foo\": true, \"bar\": false}", configuration.toJson() );
4746
}
4847

4948
@Test
5049
public void addSeparateOptionsWithChaining() {
5150
configuration.addOption( "foo", true ).addOption( "bar", false );
5251

53-
assertEquals( "{\"indent\": 1, \"foo\": true, \"bar\": false}",
54-
configuration.toJson() );
52+
assertEquals( "{\"foo\": true, \"bar\": false}", configuration.toJson() );
5553
}
5654

5755
@Test
5856
public void addSameOptionTwice() {
5957
configuration.addOption( "foo", true );
6058
configuration.addOption( "foo", false );
6159

62-
assertEquals( "{\"indent\": 1, \"foo\": false}", configuration.toJson() );
60+
assertEquals( "{\"foo\": false}", configuration.toJson() );
6361
}
6462

6563
@Test
6664
public void addSamePredefTwice() {
6765
configuration.addPredefined( "foo", true );
6866
configuration.addPredefined( "foo", false );
6967

70-
assertEquals( "{\"predef\": {\"foo\": false}, \"indent\": 1}", configuration.toJson() );
68+
assertEquals( "{\"predef\": {\"foo\": false}}", configuration.toJson() );
7169
}
7270

7371
@Test
7472
public void addOnePredef() {
7573
configuration.addPredefined( "foo", true );
7674

77-
assertEquals( "{\"predef\": {\"foo\": true}, \"indent\": 1}", configuration.toJson() );
75+
assertEquals( "{\"predef\": {\"foo\": true}}", configuration.toJson() );
7876
}
7977

8078
@Test
8179
public void addSeparatePredefsWithChaining() {
8280
configuration.addPredefined( "foo", true ).addPredefined( "bar", false );
8381

84-
assertEquals( "{\"predef\": {\"foo\": true, \"bar\": false}, \"indent\": 1}",
85-
configuration.toJson() );
82+
assertEquals( "{\"predef\": {\"foo\": true, \"bar\": false}}", configuration.toJson() );
8683
}
8784

8885
@Test
8986
public void addSeparatePredefs() {
9087
configuration.addPredefined( "foo", true );
9188
configuration.addPredefined( "bar", false );
9289

93-
assertEquals( "{\"predef\": {\"foo\": true, \"bar\": false}, \"indent\": 1}",
94-
configuration.toJson() );
90+
assertEquals( "{\"predef\": {\"foo\": true, \"bar\": false}}", configuration.toJson() );
9591
}
9692

9793
@Test
9894
public void addPredefAndOption() {
9995
configuration.addPredefined( "foo", true );
10096
configuration.addOption( "bar", false );
10197

102-
assertEquals( "{\"predef\": {\"foo\": true}, \"indent\": 1, \"bar\": false}",
103-
configuration.toJson() );
98+
assertEquals( "{\"predef\": {\"foo\": true}, \"bar\": false}", configuration.toJson() );
10499
}
105100

106101
}

com.eclipsesource.jshint.test/src/com/eclipsesource/jshint/JSHint_Test.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,12 @@ public void checkWithoutLoad() {
8383

8484
@Test( expected = NullPointerException.class )
8585
public void checkWithNullCode() {
86-
jsHint.check( null, handler );
86+
jsHint.check( (String)null, handler );
87+
}
88+
89+
@Test( expected = NullPointerException.class )
90+
public void checkWithNullText() {
91+
jsHint.check( (Text)null, handler );
8792
}
8893

8994
@Test

com.eclipsesource.jshint.test/src/com/eclipsesource/jshint/Text_Test.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
******************************************************************************/
1111
package com.eclipsesource.jshint;
1212

13+
import java.io.IOException;
1314
import java.io.Reader;
1415
import java.io.StringReader;
1516

@@ -24,6 +25,24 @@
2425

2526
public class Text_Test {
2627

28+
@Test( expected = NullPointerException.class )
29+
public void createWithNullStringFails() {
30+
new Text( (String)null );
31+
}
32+
33+
@Test( expected = NullPointerException.class )
34+
public void createWithNullReaderFails() throws IOException {
35+
new Text( (Reader)null );
36+
}
37+
38+
@Test
39+
public void createWithString() {
40+
Text textFile = new Text( "" );
41+
42+
assertEquals( "", textFile.getContent() );
43+
assertEquals( 1, textFile.getLineCount() );
44+
}
45+
2746
@Test
2847
public void emptyString() throws Exception {
2948
Reader reader = new StringReader( "" );

com.eclipsesource.jshint.test/src/com/eclipsesource/jshint/internal/JSHintRunner_Test.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,15 @@ public void illegalCharset() throws Exception {
148148
public void customLibrary() throws Exception {
149149
JSHintRunner runner = new JSHintRunner();
150150
String fakeJsHint = "JSHINT = function() { return false; };"
151-
+ "JSHINT.errors = [ { line: 23, character: 42, reason: 'test' } ]";
151+
+ "JSHINT.errors = [ { line: 1, character: 2, reason: 'test' } ]";
152152
File fakeJSHintFile = createTmpFile( fakeJsHint, "UTF-8" );
153153
File jsFile = createTmpFile( "-- ignored --", "UTF-8" );
154154
String fakeJSHintFileName = fakeJSHintFile.getAbsolutePath();
155155
String jsFileName = jsFile.getAbsolutePath();
156156

157157
runner.run( "--custom", fakeJSHintFileName, jsFileName );
158158

159-
assertThat( getSysout(), startsWith( "Problem in file " + jsFileName + " at line 23: test" ) );
159+
assertThat( getSysout(), startsWith( "Problem in file " + jsFileName + " at line 1: test" ) );
160160
}
161161

162162
@Test

com.eclipsesource.jshint.ui.test/src/com/eclipsesource/jshint/ui/internal/preferences/OptionParserUtil_Test.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,14 @@ public class OptionParserUtil_Test {
2626
public void createConfiguration() {
2727
Configuration result = OptionParserUtil.createConfiguration( "opt1: true", "predef1: false" );
2828

29-
String expected = "{\"predef\": {\"predef1\": false}, \"indent\": 1, \"opt1\": true}";
30-
assertEquals( expected, result.toJson() );
29+
assertEquals( "{\"predef\": {\"predef1\": false}, \"opt1\": true}", result.toJson() );
3130
}
3231

3332
@Test
3433
public void createConfiguration_withEmptyParameters() {
3534
Configuration result = OptionParserUtil.createConfiguration( "", "" );
3635

37-
String expected = "{\"indent\": 1}";
38-
assertEquals( expected, result.toJson() );
36+
assertEquals( "{}", result.toJson() );
3937
}
4038

4139
@Test( expected = NullPointerException.class )

com.eclipsesource.jshint.ui/src/com/eclipsesource/jshint/ui/internal/builder/JSHintBuilderVisitor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ private void check( IFile file ) throws CoreException {
101101
Text code = readContent( file );
102102
ProblemHandler handler = new MarkerHandler( new MarkerAdapter( file ), code );
103103
try {
104-
checker.check( code.getContent(), handler );
104+
checker.check( code, handler );
105105
} catch( CoreExceptionWrapper wrapper ) {
106106
throw (CoreException)wrapper.getCause();
107107
} catch( RuntimeException exception ) {

com.eclipsesource.jshint/src/com/eclipsesource/jshint/Configuration.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ public class Configuration {
2727
public Configuration() {
2828
predefs = new LinkedHashMap<String, Object>();
2929
options = new LinkedHashMap<String, Object>();
30-
options.put( "indent", Integer.valueOf( 1 ) );
3130
}
3231

3332
/**
@@ -78,6 +77,10 @@ public String toJson() {
7877
return builder.toString();
7978
}
8079

80+
Object getOption( String option ) {
81+
return options.get( option );
82+
}
83+
8184
private void addMap( StringBuilder builder, Map<String, Object> map ) {
8285
boolean first = true;
8386
for( String key : map.keySet() ) {

com.eclipsesource.jshint/src/com/eclipsesource/jshint/JSHint.java

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,11 @@
4646
public class JSHint {
4747

4848
private static final String DEFAULT_JSHINT_VERSION = "r12";
49+
private static final int DEFAULT_JSHINT_INDENT = 4;
4950
private Function jshint;
5051
private Object opts;
5152
private ScriptableObject scope;
53+
private int indent = DEFAULT_JSHINT_INDENT;
5254

5355
/**
5456
* Loads the default JSHint library.
@@ -101,11 +103,20 @@ public void configure( Configuration configuration ) {
101103
ScriptableObject scope = context.initStandardObjects();
102104
String optionsString = configuration.toJson();
103105
opts = context.evaluateString( scope, "opts = " + optionsString + ";", "[options]", 1, null );
106+
indent = determineIndent( configuration );
104107
} finally {
105108
Context.exit();
106109
}
107110
}
108111

112+
private int determineIndent( Configuration configuration ) {
113+
Object indent = configuration.getOption( "indent" );
114+
if( indent instanceof Integer ) {
115+
return ( (Integer)indent ).intValue();
116+
}
117+
return DEFAULT_JSHINT_INDENT;
118+
}
119+
109120
/**
110121
* Checks the given JavaScript code. All problems will be reported to the given problem handler.
111122
*
@@ -119,18 +130,26 @@ public boolean check( String code, ProblemHandler handler ) {
119130
if( code == null ) {
120131
throw new NullPointerException( "code is null" );
121132
}
133+
return check( new Text( code ), handler );
134+
}
135+
136+
public boolean check( Text text, ProblemHandler handler ) {
137+
if( text == null ) {
138+
throw new NullPointerException( "code is null" );
139+
}
122140
if( jshint == null ) {
123141
throw new IllegalStateException( "JSHint is not loaded" );
124142
}
125143
boolean result = true;
144+
String code = text.getContent();
126145
// Don't feed jshint with empty strings, see https://github.com/jshint/jshint/issues/615
127146
// However, consider an empty string valid
128147
if( code.trim().length() != 0 ) {
129148
Context context = Context.enter();
130149
try {
131150
result = checkCode( context, code );
132151
if( !result && handler != null ) {
133-
handleProblems( handler );
152+
handleProblems( handler, text );
134153
}
135154
} finally {
136155
Context.exit();
@@ -187,30 +206,45 @@ private Function findJSHintFunction( ScriptableObject scope ) throws IllegalArgu
187206
return (Function)object;
188207
}
189208

190-
private void handleProblems( ProblemHandler handler ) {
209+
private void handleProblems( ProblemHandler handler, Text text ) {
191210
NativeArray errors = (NativeArray)jshint.get( "errors", jshint );
192211
long length = errors.getLength();
193212
for( int i = 0; i < length; i++ ) {
194213
Object object = errors.get( i, errors );
195214
ScriptableObject error = (ScriptableObject)object;
196215
if( error != null ) {
197-
Problem problem = createProblem( error );
216+
Problem problem = createProblem( error, text );
198217
handler.handleProblem( problem );
199218
}
200219
}
201220
}
202221

203-
private ProblemImpl createProblem( ScriptableObject error ) {
222+
private ProblemImpl createProblem( ScriptableObject error, Text text ) {
204223
String reason = getPropertyAsString( error, "reason", "" );
205224
int line = getPropertyAsInt( error, "line", -1 );
206225
int character = getPropertyAsInt( error, "character", -1 );
207226
if( character > 0 ) {
208-
character -= 1;
227+
character = fixPosition( text, line, character );
209228
}
210229
String message = reason.endsWith( "." ) ? reason.substring( 0, reason.length() - 1 ) : reason;
211230
return new ProblemImpl( line, character, message );
212231
}
213232

233+
private int fixPosition( Text text, int line, int character ) {
234+
// JSHint reports physical character positions instead of a character index,
235+
// i.e. every tab character is multiplied with the indent.
236+
String string = text.getContent();
237+
int offset = text.getLineOffset( line - 1 );
238+
int indentIndex = 0;
239+
int charIndex = 0;
240+
while( indentIndex < character - 1 ) {
241+
boolean isTab = string.charAt( offset + indentIndex ) == '\t';
242+
indentIndex += isTab ? indent : 1;
243+
charIndex++;
244+
}
245+
return charIndex;
246+
}
247+
214248
private static String getPropertyAsString( ScriptableObject object,
215249
String name,
216250
String defaultValue )

com.eclipsesource.jshint/src/com/eclipsesource/jshint/Text.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import java.io.IOException;
1414
import java.io.Reader;
15+
import java.io.StringReader;
1516

1617

1718
/**
@@ -23,7 +24,24 @@ public class Text {
2324
private int lineCount = 1;
2425
private int[] lineOffsets = new int[ 200 ];
2526

27+
public Text( String text ) {
28+
if( text == null ) {
29+
throw new NullPointerException( "text is null" );
30+
}
31+
StringReader reader = new StringReader( text );
32+
try {
33+
read( reader );
34+
} catch( IOException exception ) {
35+
throw new RuntimeException( exception );
36+
} finally {
37+
reader.close();
38+
}
39+
}
40+
2641
public Text( Reader reader ) throws IOException {
42+
if( reader == null ) {
43+
throw new NullPointerException( "reader is null" );
44+
}
2745
read( reader );
2846
}
2947

@@ -44,7 +62,7 @@ public int getLineCount() {
4462
*/
4563
public int getLineOffset( int line ) {
4664
if( line >= lineCount ) {
47-
throw new IndexOutOfBoundsException( "line does not exist" );
65+
throw new IndexOutOfBoundsException( "line does not exist: " + line );
4866
}
4967
return lineOffsets[ line ];
5068
}

0 commit comments

Comments
 (0)