|
4 | 4 | "bytes" |
5 | 5 | "fmt" |
6 | 6 | "io" |
| 7 | + "strings" |
7 | 8 |
|
8 | 9 | "github.com/iancoleman/strcase" |
9 | 10 | "github.com/tdewolff/parse/v2" |
@@ -179,6 +180,162 @@ func HoistImports(source []byte) HoistedScripts { |
179 | 180 | return HoistedScripts{Hoisted: imports, Body: body} |
180 | 181 | } |
181 | 182 |
|
| 183 | +type Props struct { |
| 184 | + Ident string |
| 185 | + Statement string |
| 186 | + Generics string |
| 187 | +} |
| 188 | + |
| 189 | +func GetPropsType(source []byte) Props { |
| 190 | + defaultPropType := "Record<string, any>" |
| 191 | + ident := defaultPropType |
| 192 | + genericsIdents := make([]string, 0) |
| 193 | + generics := "" |
| 194 | + statement := "" |
| 195 | + |
| 196 | + if !bytes.Contains(source, []byte("Props")) { |
| 197 | + return Props{ |
| 198 | + Ident: ident, |
| 199 | + Statement: statement, |
| 200 | + Generics: generics, |
| 201 | + } |
| 202 | + } |
| 203 | + l := js.NewLexer(parse.NewInputBytes(source)) |
| 204 | + i := 0 |
| 205 | + pairs := make(map[byte]int) |
| 206 | + idents := make([]string, 0) |
| 207 | + |
| 208 | + start := 0 |
| 209 | + end := 0 |
| 210 | + |
| 211 | +outer: |
| 212 | + for { |
| 213 | + token, value := l.Next() |
| 214 | + |
| 215 | + if token == js.DivToken || token == js.DivEqToken { |
| 216 | + lns := bytes.Split(source[i+1:], []byte{'\n'}) |
| 217 | + if bytes.Contains(lns[0], []byte{'/'}) { |
| 218 | + token, value = l.RegExp() |
| 219 | + } |
| 220 | + } |
| 221 | + |
| 222 | + if token == js.ErrorToken { |
| 223 | + if l.Err() != io.EOF { |
| 224 | + return Props{ |
| 225 | + Ident: ident, |
| 226 | + } |
| 227 | + } |
| 228 | + break |
| 229 | + } |
| 230 | + |
| 231 | + // Common delimeters. Track their length, then skip. |
| 232 | + if token == js.WhitespaceToken || token == js.LineTerminatorToken || token == js.SemicolonToken { |
| 233 | + i += len(value) |
| 234 | + continue |
| 235 | + } |
| 236 | + |
| 237 | + if token == js.ExtendsToken { |
| 238 | + if bytes.Equal(value, []byte("extends")) { |
| 239 | + idents = append(idents, "extends") |
| 240 | + } |
| 241 | + i += len(value) |
| 242 | + continue |
| 243 | + } |
| 244 | + |
| 245 | + if pairs['{'] == 0 && pairs['('] == 0 && pairs['['] == 0 && pairs['<'] == 1 && token == js.CommaToken { |
| 246 | + idents = make([]string, 0) |
| 247 | + i += len(value) |
| 248 | + continue |
| 249 | + } |
| 250 | + |
| 251 | + if js.IsIdentifier(token) { |
| 252 | + if isKeyword(value) { |
| 253 | + i += len(value) |
| 254 | + continue |
| 255 | + } |
| 256 | + if pairs['<'] == 1 && pairs['{'] == 0 { |
| 257 | + foundExtends := false |
| 258 | + for _, id := range idents { |
| 259 | + if id == "extends" { |
| 260 | + foundExtends = true |
| 261 | + } |
| 262 | + } |
| 263 | + if !foundExtends { |
| 264 | + genericsIdents = append(genericsIdents, string(value)) |
| 265 | + } |
| 266 | + i += len(value) |
| 267 | + continue |
| 268 | + } |
| 269 | + // Note: do not check that `pairs['{'] == 0` to support named imports |
| 270 | + if pairs['('] == 0 && pairs['['] == 0 && string(value) == "Props" { |
| 271 | + ident = "Props" |
| 272 | + } |
| 273 | + idents = append(idents, string(value)) |
| 274 | + i += len(value) |
| 275 | + continue |
| 276 | + } |
| 277 | + |
| 278 | + if bytes.ContainsAny(value, "<>") { |
| 279 | + if len(idents) > 0 && idents[len(idents)-1] == "Props" { |
| 280 | + start = i |
| 281 | + ident = "Props" |
| 282 | + idents = make([]string, 0) |
| 283 | + } |
| 284 | + for _, c := range value { |
| 285 | + if c == '<' { |
| 286 | + pairs['<']++ |
| 287 | + i += len(value) |
| 288 | + continue |
| 289 | + } |
| 290 | + if c == '>' { |
| 291 | + pairs['<']-- |
| 292 | + if pairs['<'] == 0 { |
| 293 | + end = i |
| 294 | + break outer |
| 295 | + } |
| 296 | + } |
| 297 | + } |
| 298 | + } |
| 299 | + |
| 300 | + if token == js.QuestionToken || (pairs['{'] == 0 && token == js.ColonToken) { |
| 301 | + idents = make([]string, 0) |
| 302 | + idents = append(idents, "extends") |
| 303 | + } |
| 304 | + |
| 305 | + // Track opening and closing braces |
| 306 | + if js.IsPunctuator(token) { |
| 307 | + if value[0] == '{' || value[0] == '(' || value[0] == '[' { |
| 308 | + idents = make([]string, 0) |
| 309 | + pairs[value[0]]++ |
| 310 | + i += len(value) |
| 311 | + continue |
| 312 | + } else if value[0] == '}' { |
| 313 | + pairs['{']-- |
| 314 | + if pairs['<'] == 0 && pairs['{'] == 0 && ident != defaultPropType { |
| 315 | + end = i |
| 316 | + break outer |
| 317 | + } |
| 318 | + } else if value[0] == ')' { |
| 319 | + pairs['(']-- |
| 320 | + } else if value[0] == ']' { |
| 321 | + pairs['[']-- |
| 322 | + } |
| 323 | + } |
| 324 | + |
| 325 | + // Track our current position |
| 326 | + i += len(value) |
| 327 | + } |
| 328 | + if len(genericsIdents) > 0 && ident != defaultPropType { |
| 329 | + generics = fmt.Sprintf("<%s>", strings.Join(genericsIdents, ", ")) |
| 330 | + statement = strings.TrimSpace(string(source[start:end])) |
| 331 | + } |
| 332 | + return Props{ |
| 333 | + Ident: ident, |
| 334 | + Statement: statement, |
| 335 | + Generics: generics, |
| 336 | + } |
| 337 | +} |
| 338 | + |
182 | 339 | func isIdentifier(value []byte) bool { |
183 | 340 | valid := true |
184 | 341 | for i, b := range value { |
|
0 commit comments