@@ -12,130 +12,121 @@ export const expressionLanguageLinterSource = (state: EditorState) => {
1212 const config = getExpressionLanguageConfig ( state ) ;
1313 const diagnostics : Diagnostic [ ] = [ ] ;
1414
15- syntaxTree ( state ) . cursor ( ) . iterate ( node => {
15+ const processNode = async ( node : any ) => {
1616 const { from, to, type : { id } } = node ;
17-
1817 let identifier : string | undefined ;
1918 switch ( id ) {
20- case 0 : {
21- if ( state . doc . length === 0 || from === 0 ) {
22- // Don't show error on empty doc (even though it is an error)
23- return ;
24- }
25-
26- identifier = state . sliceDoc ( from , to ) ;
27- if ( identifier . length === 0 ) {
28- diagnostics . push ( { from, to : node . node . parent ?. parent ?. to ?? to , severity : 'error' , message : `Expression expected` } ) ;
29- } else {
30- const type = / ^ [ a - z A - Z _ ] + [ a - z A - Z _ 0 - 9 ] * $ / . test ( identifier ) ? 'identifier' : 'operator' ;
31- diagnostics . push ( { from, to, severity : 'error' , message : `Unexpected ${ type } <code>${ identifier } </code>` } ) ;
32- }
33-
19+ case 0 : {
20+ if ( state . doc . length === 0 || from === 0 ) {
21+ return ;
22+ }
23+ identifier = state . sliceDoc ( from , to ) ;
24+ if ( identifier . length === 0 ) {
25+ diagnostics . push ( { from, to : node . node . parent ?. parent ?. to ?? to , severity : 'error' , message : `Expression expected` } ) ;
26+ } else {
27+ const type = / ^ [ a - z A - Z _ ] + [ a - z A - Z _ 0 - 9 ] * $ / . test ( identifier ) ? 'identifier' : 'operator' ;
28+ diagnostics . push ( { from, to, severity : 'error' , message : `Unexpected ${ type } <code>${ identifier } </code>` } ) ;
29+ }
30+ return ;
31+ }
32+ case Arguments : {
33+ const fn = await resolveFunctionDefinition ( node . node . prevSibling , state , config ) ;
34+ const args = fn ?. args ;
35+ if ( ! args ) {
3436 return ;
3537 }
36- case Arguments : {
37- const fn = resolveFunctionDefinition ( node . node . prevSibling , state , config ) ;
38- const args = fn ?. args ;
39- if ( ! args ) {
40- return ;
38+ const argCountMin = args . reduce ( ( count , arg ) => count + Number ( ! arg . optional ) , 0 ) ;
39+ const argCountMax = args . length ;
40+ const argumentCountHintFn = ( ) => `<code>${ fn ?. name } </code> takes ${ argCountMin == argCountMax ? `exactly ${ argCountMax } ` : `${ argCountMin } –${ argCountMax } ` } argument${ argCountMax == 1 ? '' : 's' } ` ;
41+ let i = 0 ;
42+ for ( let n = node . node . firstChild ; n != null ; n = n . nextSibling ) {
43+ if ( n . type . is ( BlockComment ) ) {
44+ continue ;
4145 }
42- const argCountMin = args . reduce ( ( count , arg ) => count + Number ( ! arg . optional ) , 0 ) ;
43- const argCountMax = args . length ;
44- const argumentCountHintFn = ( ) => `<code>${ fn . name } </code> takes ${ argCountMin == argCountMax ? `exactly ${ argCountMax } ` : `${ argCountMin } –${ argCountMax } ` } argument${ argCountMax == 1 ? '' : 's' } ` ;
45- let i = 0 ;
46-
47- for ( let n = node . node . firstChild ; n != null ; n = n . nextSibling ) {
48- if ( n . type . is ( BlockComment ) ) {
49- continue ;
50- }
51-
52- if ( i > argCountMax - 1 ) {
53- diagnostics . push ( { from : n . from , to : n . to , severity : 'warning' , message : `Unexpected argument – ${ argumentCountHintFn ( ) } ` } ) ;
54- continue ;
55- }
56-
57- const typesUsed = Array . from ( resolveTypes ( state , n , config ) ) ;
58- const typesExpected = args [ i ] . type ;
59-
60- if ( typesExpected && ! typesExpected . includes ( ELScalar . Any ) && ! typesUsed . some ( x => typesExpected . includes ( x ) ) ) {
61- diagnostics . push ( {
62- from : n . from ,
63- to : n . to ,
64- severity : 'error' ,
65- message : `<code>${ typesExpected . join ( '|' ) } </code> expected, got <code>${ typesUsed . join ( '|' ) } </code>`
66- } ) ;
67- }
68- i ++ ;
46+ if ( i > argCountMax - 1 ) {
47+ diagnostics . push ( { from : n . from , to : n . to , severity : 'warning' , message : `Unexpected argument – ${ argumentCountHintFn ( ) } ` } ) ;
48+ continue ;
6949 }
70-
71- if ( i < argCountMin ) {
72- diagnostics . push ( { from : node . from , to : node . to , severity : 'error' , message : `Too few arguments – ${ argumentCountHintFn ( ) } ` } ) ;
50+ const typesUsed = Array . from ( await resolveTypes ( state , n , config ) ) ;
51+ const typesExpected = args [ i ] . type ;
52+ if ( typesExpected && ! typesExpected . includes ( ELScalar . Any ) && ! typesUsed . some ( x => typesExpected . includes ( x ) ) ) {
53+ diagnostics . push ( {
54+ from : n . from ,
55+ to : n . to ,
56+ severity : 'error' ,
57+ message : `<code>${ typesExpected . join ( '|' ) } </code> expected, got <code>${ typesUsed . join ( '|' ) } </code>`
58+ } ) ;
7359 }
74-
75- break ;
60+ i ++ ;
7661 }
77- case Property :
78- case Method : {
79- const leftArgument = node . node . parent ?. firstChild ?. node ;
80- const types = Array . from ( resolveTypes ( state , leftArgument , config ) ) ;
81- identifier = state . sliceDoc ( from , to ) ;
82-
83- if ( ! types . find ( type => resolveIdentifier ( id , identifier , config . types ?. [ type ] ) ) ) {
84- diagnostics . push ( { from, to, severity : 'error' , message : `${ node . name } <code>${ identifier } </code> not found in <code>${ types . join ( '|' ) } </code>` } ) ;
85- }
86-
87- break ;
62+ if ( i < argCountMin ) {
63+ diagnostics . push ( { from : node . from , to : node . to , severity : 'error' , message : `Too few arguments – ${ argumentCountHintFn ( ) } ` } ) ;
8864 }
89- case Variable :
90- case Function : {
91- identifier = state . sliceDoc ( from , node . node . firstChild ? node . node . firstChild . from - 1 : to ) ;
92- if ( ! resolveIdentifier ( id , identifier , config ) ) {
93- diagnostics . push ( { from, to, severity : 'error' , message : `${ node . node . name } <code>${ identifier } </code> not found` } ) ;
94- }
95-
96- break ;
65+ break ;
66+ }
67+ case Property :
68+ case Method : {
69+ const leftArgument = node . node . parent ?. firstChild ?. node ;
70+ const types = Array . from ( await resolveTypes ( state , leftArgument , config ) ) ;
71+ identifier = state . sliceDoc ( from , to ) ;
72+ if ( ! types . find ( type => resolveIdentifier ( id , identifier , config . types ?. [ type ] ) ) ) {
73+ diagnostics . push ( { from, to, severity : 'error' , message : `${ node . name } <code>${ identifier } </code> not found in <code>${ types . join ( '|' ) } </code>` } ) ;
74+ }
75+ break ;
76+ }
77+ case Variable :
78+ case Function : {
79+ identifier = state . sliceDoc ( from , node . node . firstChild ? node . node . firstChild . from - 1 : to ) ;
80+ if ( ! resolveIdentifier ( id , identifier , config ) ) {
81+ diagnostics . push ( { from, to, severity : 'error' , message : `${ node . node . name } <code>${ identifier } </code> not found` } ) ;
9782 }
98- case BinaryExpression : {
99- const operatorNode = node . node . getChild ( OperatorKeyword ) ;
100- if ( operatorNode ) {
101- const operator = state . sliceDoc ( operatorNode . from , operatorNode . to ) ;
102- const leftArgument = node . node . firstChild ;
103- const rightArgument = node . node . lastChild ;
104- if ( operator === 'in' ) {
105- const types = resolveTypes ( state , rightArgument , config ) ;
106- if ( ! types . has ( ELScalar . Array ) ) {
107- diagnostics . push ( { from : rightArgument . from , to : rightArgument . to , severity : 'error' , message : `<code>${ ELScalar . Array } </code> expected, got <code>${ [ ...types ] . join ( '|' ) } </code>` } ) ;
108- }
109- } else if ( [ "contains" , "starts with" , "ends with" , "matches" ] . includes ( operator ) ) {
110- // Both sides must be string
111- const leftTypes = resolveTypes ( state , leftArgument , config ) ;
112- const rightTypes = resolveTypes ( state , rightArgument , config ) ;
113- if ( ! leftTypes . has ( ELScalar . String ) ) {
114- diagnostics . push ( { from : leftArgument . from , to : leftArgument . to , severity : 'error' , message : `<code>string</code> expected, got <code>${ [ ...leftTypes ] . join ( '|' ) } </code>` } ) ;
115- }
116- if ( ! rightTypes . has ( ELScalar . String ) ) {
117- diagnostics . push ( { from : rightArgument . from , to : rightArgument . to , severity : 'error' , message : `<code>string</code> expected, got <code>${ [ ...rightTypes ] . join ( '|' ) } </code>` } ) ;
118- }
83+ break ;
84+ }
85+ case BinaryExpression : {
86+ const operatorNode = node . node . getChild ( OperatorKeyword ) ;
87+ if ( operatorNode ) {
88+ const operator = state . sliceDoc ( operatorNode . from , operatorNode . to ) ;
89+ const leftArgument = node . node . firstChild ;
90+ const rightArgument = node . node . lastChild ;
91+ if ( operator === 'in' ) {
92+ const types = await resolveTypes ( state , rightArgument , config ) ;
93+ if ( ! types . has ( ELScalar . Array ) ) {
94+ diagnostics . push ( { from : rightArgument . from , to : rightArgument . to , severity : 'error' , message : `<code>${ ELScalar . Array } </code> expected, got <code>${ [ ...types ] . join ( '|' ) } </code>` } ) ;
95+ }
96+ } else if ( [ "contains" , "starts with" , "ends with" , "matches" ] . includes ( operator ) ) {
97+ // Both sides must be string
98+ const leftTypes = await resolveTypes ( state , leftArgument , config ) ;
99+ const rightTypes = await resolveTypes ( state , rightArgument , config ) ;
100+ if ( ! leftTypes . has ( ELScalar . String ) ) {
101+ diagnostics . push ( { from : leftArgument . from , to : leftArgument . to , severity : 'error' , message : `<code>string</code> expected, got <code>${ [ ...leftTypes ] . join ( '|' ) } </code>` } ) ;
102+ }
103+ if ( ! rightTypes . has ( ELScalar . String ) ) {
104+ diagnostics . push ( { from : rightArgument . from , to : rightArgument . to , severity : 'error' , message : `<code>string</code> expected, got <code>${ [ ...rightTypes ] . join ( '|' ) } </code>` } ) ;
119105 }
120106 }
121- break ;
122107 }
108+ break ;
109+ }
123110 }
124-
125111 if ( identifier && node . node . parent ?. type . isError ) {
126112 diagnostics . push ( { from, to, severity : 'error' , message : `Unexpected identifier <code>${ identifier } </code>` } ) ;
127113 }
128- } ) ;
114+ } ;
129115
130- diagnostics . forEach ( d => {
131- d . renderMessage = ( ) => {
132- const span = document . createElement ( 'span' ) ;
133- span . innerHTML = d . message ;
134- return span ;
135- } ;
136- } ) ;
137-
138- return diagnostics ;
116+ return ( async ( ) => {
117+ const cursor = syntaxTree ( state ) . cursor ( ) ;
118+ while ( cursor . next ( ) ) {
119+ await processNode ( cursor ) ;
120+ }
121+ diagnostics . forEach ( d => {
122+ d . renderMessage = ( ) => {
123+ const span = document . createElement ( 'span' ) ;
124+ span . innerHTML = d . message ;
125+ return span ;
126+ } ;
127+ } ) ;
128+ return diagnostics ;
129+ } ) ( ) ;
139130} ;
140131
141132export const expressionLanguageLinter = linter ( view => expressionLanguageLinterSource ( view . state ) ) ;
0 commit comments