• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / js-slang / 14529259416

18 Apr 2025 03:46AM UTC coverage: 80.538%. Remained the same
14529259416

Pull #1757

github

web-flow
Merge 63eac9783 into fea2b4cad
Pull Request #1757: Language options

3458 of 4687 branches covered (73.78%)

Branch coverage included in aggregate %.

15 of 88 new or added lines in 6 files covered. (17.05%)

84 existing lines in 6 files now uncovered.

10811 of 13030 relevant lines covered (82.97%)

142544.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

25.88
/src/parser/source/typed/index.ts
1
import { parse as babelParse } from '@babel/parser'
74✔
2
import { Options as AcornOptions } from 'acorn'
3
import { Program } from 'estree'
4

5
import { Context } from '../../..'
6
import { DEFAULT_ECMA_VERSION } from '../../../constants'
74✔
7
import * as TypedES from '../../../typeChecker/tsESTree'
8
import { checkForTypeErrors } from '../../../typeChecker/typeErrorChecker'
74✔
9
import { FatalSyntaxError } from '../../errors'
74✔
10
import {
74✔
11
  createAcornParserOptions,
12
  defaultBabelOptions,
13
  positionToSourceLocation
14
} from '../../utils'
15
import { SourceParser } from '..'
74✔
16
import TypeParser from './typeParser'
74✔
17
import { transformBabelASTToESTreeCompliantAST } from './utils'
74✔
18

19
export class SourceTypedParser extends SourceParser {
74✔
20
  parse(
21
    programStr: string,
22
    context: Context,
23
    options?: Partial<AcornOptions>,
24
    throwOnError?: boolean
25
  ): Program | null {
26
    // Parse with acorn type parser first to catch errors such as
27
    // import/export not at top level, trailing commas, missing semicolons
28
    try {
114✔
29
      TypeParser.parse(
114✔
30
        programStr,
31
        createAcornParserOptions(DEFAULT_ECMA_VERSION, context.errors, options)
32
      )
33
    } catch (error) {
34
      if (error instanceof SyntaxError) {
2✔
35
        error = new FatalSyntaxError(
2✔
36
          positionToSourceLocation((error as any).loc, options?.sourceFile),
37
          error.toString()
38
        )
39
      }
40

41
      if (throwOnError) throw error
2!
42
      context.errors.push(error)
2✔
43
      return null
2✔
44
    }
45

46
    // Parse again with babel parser to capture all type syntax
47
    // and catch remaining syntax errors not caught by acorn type parser
48
    const ast = babelParse(programStr, {
112✔
49
      ...defaultBabelOptions,
50
      sourceFilename: options?.sourceFile,
51
      errorRecovery: throwOnError ?? true
224✔
52
    })
53

54
    if (ast.errors.length) {
112✔
55
      ast.errors
1✔
56
        .filter(error => error instanceof SyntaxError)
1✔
57
        .forEach(error => {
58
          context.errors.push(
1✔
59
            new FatalSyntaxError(
60
              positionToSourceLocation((error as any).loc, options?.sourceFile),
61
              error.toString()
62
            )
63
          )
64
        })
65

66
      return null
1✔
67
    }
68

69
    const typedProgram: TypedES.Program = ast.program as TypedES.Program
111✔
70
    if (context.prelude !== programStr) {
111✔
71
      // Check for any declaration only if the program is not the prelude
72
      checkForAnyDeclaration(typedProgram, context)
111✔
73
    }
74
    const typedCheckedProgram: Program = checkForTypeErrors(typedProgram, context)
111✔
75
    transformBabelASTToESTreeCompliantAST(typedCheckedProgram)
111✔
76

77
    return typedCheckedProgram
111✔
78
  }
79

80
  toString(): string {
UNCOV
81
    return 'SourceTypedParser'
×
82
  }
83
}
84

85
function checkForAnyDeclaration(program: TypedES.Program, context: Context) {
86
  function parseConfigOption(option: string | undefined) {
87
    return option === 'true' || option === undefined
555✔
88
  }
89

90
  const config = {
111✔
91
    allowAnyInVariables: parseConfigOption(context.languageOptions['typedAllowAnyInVariables']),
92
    allowAnyInParameters: parseConfigOption(context.languageOptions['typedAllowAnyInParameters']),
93
    allowAnyInReturnType: parseConfigOption(context.languageOptions['typedAllowAnyInReturnType']),
94
    allowAnyInTypeAnnotationParameters: parseConfigOption(
95
      context.languageOptions['typedAllowAnyInTypeAnnotationParameters']
96
    ),
97
    allowAnyInTypeAnnotationReturnType: parseConfigOption(
98
      context.languageOptions['typedAllowAnyInTypeAnnotationReturnType']
99
    )
100
  }
101

102
  function pushAnyUsageError(message: string, node: TypedES.Node) {
NEW
103
    if (node.loc) {
×
NEW
104
      context.errors.push(new FatalSyntaxError(node.loc, message))
×
105
    }
106
  }
107

108
  function isAnyType(node: TypedES.TSTypeAnnotation | undefined) {
NEW
109
    return node?.typeAnnotation?.type === 'TSAnyKeyword' || node?.typeAnnotation === undefined
×
110
  }
111

112
  function checkNode(node: TypedES.Node) {
NEW
113
    switch (node.type) {
×
114
      case 'VariableDeclaration': {
NEW
115
        node.declarations.forEach(decl => {
×
NEW
116
          const tsType = (decl as any).id?.typeAnnotation
×
NEW
117
          if (!config.allowAnyInVariables && isAnyType(tsType)) {
×
NEW
118
            pushAnyUsageError('Usage of "any" in variable declaration is not allowed.', node)
×
119
          }
NEW
120
          if (decl.init) {
×
121
            // check for lambdas
NEW
122
            checkNode(decl.init)
×
123
          }
124
        })
NEW
125
        break
×
126
      }
127
      case 'FunctionDeclaration': {
NEW
128
        if (!config.allowAnyInParameters || !config.allowAnyInReturnType) {
×
NEW
129
          const func = node as any
×
130
          // Check parameters
NEW
131
          func.params?.forEach((param: any) => {
×
NEW
132
            if (!config.allowAnyInParameters && isAnyType(param.typeAnnotation)) {
×
NEW
133
              pushAnyUsageError('Usage of "any" in function parameter is not allowed.', param)
×
134
            }
135
          })
136
          // Check return type
NEW
137
          if (!config.allowAnyInReturnType && isAnyType(func.returnType)) {
×
NEW
138
            pushAnyUsageError('Usage of "any" in function return type is not allowed.', node)
×
139
          }
NEW
140
          checkNode(node.body)
×
141
        }
NEW
142
        break
×
143
      }
144
      case 'ArrowFunctionExpression': {
NEW
145
        if (!config.allowAnyInParameters || !config.allowAnyInReturnType) {
×
NEW
146
          const arrow = node as any
×
147
          // Check parameters
NEW
148
          arrow.params?.forEach((param: any) => {
×
NEW
149
            if (!config.allowAnyInParameters && isAnyType(param.typeAnnotation)) {
×
NEW
150
              pushAnyUsageError('Usage of "any" in arrow function parameter is not allowed.', param)
×
151
            }
152
          })
153
          // Recursively check return type if present
NEW
154
          if (!config.allowAnyInReturnType && isAnyType(arrow.returnType)) {
×
NEW
155
            pushAnyUsageError('Usage of "any" in arrow function return type is not allowed.', arrow)
×
156
          }
NEW
157
          if (
×
158
            !config.allowAnyInReturnType &&
×
NEW
159
            arrow.params?.some((param: any) => isAnyType(param.typeAnnotation))
×
160
          ) {
NEW
161
            pushAnyUsageError('Usage of "any" in arrow function return type is not allowed.', arrow)
×
162
          }
NEW
163
          checkNode(node.body)
×
164
        }
NEW
165
        break
×
166
      }
167
      case 'ReturnStatement': {
NEW
168
        if (node.argument) {
×
NEW
169
          checkNode(node.argument)
×
170
        }
NEW
171
        break
×
172
      }
173
      case 'BlockStatement':
NEW
174
        node.body.forEach(checkNode)
×
NEW
175
        break
×
176
      default:
NEW
177
        break
×
178
    }
179
  }
180

181
  function checkTSNode(node: TypedES.Node) {
NEW
182
    if (!node) {
×
183
      // Happens when there is no type annotation
184
      // This should have been caught by checkNode function
NEW
185
      return
×
186
    }
NEW
187
    switch (node.type) {
×
188
      case 'VariableDeclaration': {
NEW
189
        node.declarations.forEach(decl => {
×
NEW
190
          const tsType = (decl as any).id?.typeAnnotation
×
NEW
191
          checkTSNode(tsType)
×
192
        })
NEW
193
        break
×
194
      }
195
      case 'TSTypeAnnotation': {
NEW
196
        const annotation = node as TypedES.TSTypeAnnotation
×
197
        // If it's a function type annotation, check params and return
NEW
198
        if (annotation.typeAnnotation?.type === 'TSFunctionType') {
×
NEW
199
          annotation.typeAnnotation.parameters?.forEach(param => {
×
200
            // Recursively check nested TSTypeAnnotations in parameters
NEW
201
            if (!config.allowAnyInTypeAnnotationParameters && isAnyType(param.typeAnnotation)) {
×
NEW
202
              pushAnyUsageError(
×
203
                'Usage of "any" in type annotation\'s function parameter is not allowed.',
204
                param
205
              )
206
            }
NEW
207
            if (param.typeAnnotation) {
×
NEW
208
              checkTSNode(param.typeAnnotation)
×
209
            }
210
          })
NEW
211
          const returnAnno = (annotation.typeAnnotation as TypedES.TSFunctionType).typeAnnotation
×
NEW
212
          if (!config.allowAnyInTypeAnnotationReturnType && isAnyType(returnAnno)) {
×
NEW
213
            pushAnyUsageError(
×
214
              'Usage of "any" in type annotation\'s function return type is not allowed.',
215
              annotation
216
            )
217
          }
218
          // Recursively check nested TSTypeAnnotations in return type
NEW
219
          checkTSNode(returnAnno)
×
220
        }
NEW
221
        break
×
222
      }
223
      case 'FunctionDeclaration': {
224
        // Here we also check param type annotations + return type via config
NEW
225
        if (
×
226
          !config.allowAnyInTypeAnnotationParameters ||
×
227
          !config.allowAnyInTypeAnnotationReturnType
228
        ) {
NEW
229
          const func = node as any
×
230
          // Check parameters
NEW
231
          if (!config.allowAnyInTypeAnnotationParameters) {
×
NEW
232
            func.params?.forEach((param: any) => {
×
NEW
233
              checkTSNode(param.typeAnnotation)
×
234
            })
235
          }
236
          // Recursively check the function return type annotation
NEW
237
          checkTSNode(func.returnType)
×
238
        }
NEW
239
        break
×
240
      }
241
      case 'BlockStatement':
NEW
242
        node.body.forEach(checkTSNode)
×
NEW
243
        break
×
244
      default:
NEW
245
        break
×
246
    }
247
  }
248

249
  if (!config.allowAnyInVariables || !config.allowAnyInParameters || !config.allowAnyInReturnType) {
111!
NEW
250
    program.body.forEach(checkNode)
×
251
  }
252
  if (!config.allowAnyInTypeAnnotationParameters || !config.allowAnyInTypeAnnotationReturnType) {
111!
NEW
253
    program.body.forEach(checkTSNode)
×
254
  }
255
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc