@@ -26,12 +26,11 @@ const blocked_statements = [
26
26
'\\bxit\\(' ,
27
27
'\\bdebugger;' ,
28
28
'from \\\'rxjs/Rx\\\'' ,
29
+ '\\r'
29
30
] ;
30
31
31
32
const sourceFolders = [ './src' , './e2e' ] ;
32
- const libRoot = 'src/lib' ;
33
33
const blockedRegex = new RegExp ( blocked_statements . join ( '|' ) , 'g' ) ;
34
- const importRegex = / f r o m \s + ' ( .* ) ' ; / g;
35
34
36
35
/*
37
36
* Verify that the current PR is not adding any forbidden identifiers.
@@ -47,69 +46,105 @@ findTestFiles()
47
46
. then ( files => files . map ( name => ( { name, content : fs . readFileSync ( name , 'utf-8' ) } ) ) )
48
47
49
48
/* Run checks against content of the filtered files. */
50
- . then ( diffList => {
49
+ . then ( diffList => findErrors ( diffList ) )
51
50
52
- return diffList . reduce ( ( errors , diffFile ) => {
53
- let fileName = diffFile . name ;
54
- let content = diffFile . content . split ( '\n' ) ;
55
- let lineCount = 0 ;
51
+ /* Groups similar errors to simplify console output */
52
+ . then ( errors => groupErrors ( errors ) )
56
53
57
- content . forEach ( line => {
58
- lineCount ++ ;
54
+ /* Print the resolved errors to the console */
55
+ . then ( errors => printErrors ( errors ) )
56
+
57
+ . catch ( err => {
58
+ // An error occurred in this script. Output the error and the stack.
59
+ console . error ( err . stack || err ) ;
60
+ process . exit ( 2 ) ;
61
+ } ) ;
62
+
63
+ /**
64
+ * Finds errors inside of changed files by running them against the blocked statements.
65
+ * @param {{name: string, content: string}[] } diffList
66
+ */
67
+ function findErrors ( diffList ) {
68
+ return diffList . reduce ( ( errors , diffFile ) => {
69
+ let fileName = diffFile . name ;
70
+ let content = diffFile . content . split ( '\n' ) ;
71
+ let lineNumber = 0 ;
59
72
60
- let matches = line . match ( blockedRegex ) ;
61
- let scopeImport = path . relative ( libRoot , fileName ) . startsWith ( '..' )
62
- ? isRelativeScopeImport ( fileName , line )
63
- : false ;
73
+ content . forEach ( line => {
74
+ lineNumber ++ ;
64
75
65
- if ( matches || scopeImport ) {
76
+ let matches = line . match ( blockedRegex ) ;
66
77
67
- let error = {
68
- fileName : fileName ,
69
- lineNumber : lineCount ,
70
- statement : matches && matches [ 0 ] || scopeImport . invalidStatement
71
- } ;
78
+ if ( matches ) {
72
79
73
- if ( scopeImport ) {
74
- error . messages = [
75
- 'You are using an import statement, which imports a file from another scope package.' ,
76
- `Please import the file by using the following path: ${ scopeImport . scopeImportPath } `
77
- ] ;
78
- }
80
+ errors . push ( {
81
+ fileName,
82
+ lineNumber,
83
+ statement : matches [ 0 ]
84
+ } ) ;
79
85
80
- errors . push ( error ) ;
81
- }
82
- } ) ;
86
+ }
87
+ } ) ;
83
88
84
- return errors ;
89
+ return errors ;
85
90
86
- } , [ ] ) ;
87
- } )
91
+ } , [ ] ) ;
92
+ }
88
93
89
- /* Print the resolved errors to the console */
90
- . then ( errors => {
91
- if ( errors . length > 0 ) {
92
- console . error ( 'Error: You are using one or more blocked statements:\n' ) ;
94
+ /**
95
+ * Groups similar errors in the same file which are present over a range of lines.
96
+ * @param {{fileName: string, lineNumber: number, statement: string}[] } errors
97
+ */
98
+ function groupErrors ( errors ) {
93
99
94
- errors . forEach ( entry => {
95
- if ( entry . messages ) {
96
- entry . messages . forEach ( message => console . error ( ` ${ message } ` ) )
97
- }
100
+ let initialError , initialLine , previousLine ;
98
101
99
- console . error ( ` ${ entry . fileName } @${ entry . lineNumber } , Statement: ${ entry . statement } .\n` ) ;
100
- } ) ;
102
+ return errors . filter ( error => {
101
103
102
- process . exit ( 1 ) ;
104
+ if ( initialError && isSimilarError ( error ) ) {
105
+ previousLine = error . lineNumber ;
106
+
107
+ // Overwrite the lineNumber with a string, which indicates the range of lines.
108
+ initialError . lineNumber = `${ initialLine } -${ previousLine } ` ;
109
+
110
+ return false ;
103
111
}
104
- } )
105
112
106
- . catch ( err => {
107
- // An error occurred in this script. Output the error and the stack.
108
- console . error ( 'An error occurred during execution:' ) ;
109
- console . error ( err . stack || err ) ;
110
- process . exit ( 2 ) ;
113
+ initialLine = previousLine = error . lineNumber ;
114
+ initialError = error ;
115
+
116
+ return true ;
111
117
} ) ;
112
118
119
+ /** Checks whether the given error is similar to the previous one. */
120
+ function isSimilarError ( error ) {
121
+ return initialError . fileName === error . fileName &&
122
+ initialError . statement === error . statement &&
123
+ previousLine === ( error . lineNumber - 1 ) ;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Prints all errors to the console and terminates the process if errors were present.
129
+ * @param {{fileName: string, lineNumber: number, statement: string}[] } errors
130
+ */
131
+ function printErrors ( errors ) {
132
+ if ( errors . length > 0 ) {
133
+
134
+ console . error ( 'Error: You are using one or more blocked statements:\n' ) ;
135
+
136
+ errors . forEach ( entry => {
137
+
138
+ // Stringify the statement to represent line-endings or other unescaped characters.
139
+ let statement = JSON . stringify ( entry . statement ) ;
140
+
141
+ console . error ( ` ${ entry . fileName } @${ entry . lineNumber } , Statement: ${ statement } .\n` ) ;
142
+ } ) ;
143
+
144
+ // Exit the process with an error exit code to notify the CI.
145
+ process . exit ( 1 ) ;
146
+ }
147
+ }
113
148
114
149
/**
115
150
* Resolves all files, which should run against the forbidden identifiers check.
@@ -120,8 +155,8 @@ function findTestFiles() {
120
155
return findChangedFiles ( ) ;
121
156
}
122
157
123
- var files = sourceFolders . map ( folder => {
124
- return glob ( `${ folder } /**/*.ts ` ) ;
158
+ let files = sourceFolders . map ( folder => {
159
+ return glob ( `${ folder } /**/*` ) ;
125
160
} ) . reduce ( ( files , fileSet ) => files . concat ( fileSet ) , [ ] ) ;
126
161
127
162
return Promise . resolve ( files ) ;
@@ -150,72 +185,6 @@ function findChangedFiles() {
150
185
} ) ;
151
186
}
152
187
153
- /**
154
- * Checks the line for any relative imports of a scope package, which should be imported by using
155
- * the scope package name instead of the relative path.
156
- * @param fileName Filename to validate the path
157
- * @param line Line to be checked.
158
- */
159
- function isRelativeScopeImport ( fileName , line ) {
160
- if ( fileName . startsWith ( libRoot ) ) {
161
- return false ;
162
- }
163
-
164
- let importMatch = importRegex . exec ( line ) ;
165
-
166
- // We have to reset the last index of the import regex, otherwise we
167
- // would have incorrect matches in the next execution.
168
- importRegex . lastIndex = 0 ;
169
-
170
- // Skip the check if the current line doesn't match any imports.
171
- if ( ! importMatch ) {
172
- return false ;
173
- }
174
-
175
- let importPath = importMatch [ 1 ] ;
176
-
177
- // Skip the check when the import doesn't start with a dot, because the line
178
- // isn't importing any file relatively. Also applies to scope packages starting
179
- // with `@`.
180
- if ( ! importPath . startsWith ( '.' ) ) {
181
- return false ;
182
- }
183
-
184
- // Transform the relative import path into an absolute path.
185
- importPath = path . resolve ( path . dirname ( fileName ) , importPath ) ;
186
-
187
- let fileScope = findScope ( fileName ) ;
188
- let importScope = findScope ( importPath ) ;
189
-
190
- if ( fileScope . path !== importScope . path ) {
191
-
192
- // Creates a valid import statement, which uses the correct scope package.
193
- let validImportPath = `@angular/material` ;
194
-
195
- return {
196
- fileScope : fileScope . name ,
197
- importScope : importScope . name ,
198
- invalidStatement : importMatch [ 0 ] ,
199
- scopeImportPath : validImportPath
200
- }
201
- }
202
-
203
- function findScope ( filePath ) {
204
- // Normalize the filePath, to avoid issues with the different environments path delimiter.
205
- filePath = path . normalize ( filePath ) ;
206
-
207
- // Iterate through all scope paths and try to find them inside of the file path.
208
- var scopePath = filePath . indexOf ( path . normalize ( libRoot ) ) == - 1 ? libRoot : filePath ;
209
-
210
- // Return an object containing the name of the scope and the associated path.
211
- return {
212
- name : path . basename ( scopePath ) ,
213
- path : scopePath
214
- }
215
- }
216
-
217
- }
218
-
219
188
/**
220
189
* Executes a process command and wraps it inside of a promise.
221
190
* @returns {Promise.<String> }
0 commit comments