UNPKG

4.17 kBJavaScriptView Raw
1'use strict';
2
3// Per https://github.com/es-shims/es-shim-api :
4// In every way possible, the package must attempt to make itself
5// robust against the environment being modified after it is required.
6const { prototype } = Function;
7const { toString } = prototype;
8const { apply } = Reflect;
9const { defineProperty, freeze, hasOwnProperty } = Object;
10
11const tokenize = require('./lib/quick-and-dirty-lexer.js');
12
13const memoTable = new WeakMap();
14
15// eslint-disable-next-line complexity
16function arityOf(fun) {
17 if (memoTable.has(fun)) {
18 return memoTable.get(fun);
19 }
20 const str = apply(toString, fun, []);
21
22 // Between the parentheses around the formals.
23 let inFormalParameterList = false;
24 // Count of open brackets ('[', '(', '{') that have been seen without a corresponding
25 // close bracket.
26 let bracketDepth = 0;
27 // Number of formal parameters seen so far. This includes any rest param.
28 let declaredParameterCount = 0;
29 // True if the formal parameter list has a ... pattern.
30 let hasRestParam = false;
31
32 // Action to apply to the next token processed. This allows equiv of LA(1)
33 // without implementing pushback.
34 let onNext = null; // Special action for next token.
35
36 function maybeCountParam(next) {
37 if (next !== ')') {
38 ++declaredParameterCount;
39 }
40 }
41
42 tokenLoop:
43 for (const tok of tokenize(str)) {
44 if (onNext) {
45 const onTok = onNext;
46 onNext = null;
47 onTok(tok);
48 }
49 switch (tok) {
50 case '(':
51 if (!bracketDepth) {
52 inFormalParameterList = true;
53 onNext = maybeCountParam;
54 }
55 // fallthrough
56 case '{':
57 case '[':
58 // This works even if str has a dynamic name like
59 // [nameExpression](formal,parameter,list) {body}
60 ++bracketDepth;
61 break;
62 case ',':
63 if (inFormalParameterList && bracketDepth === 1) {
64 onNext = maybeCountParam;
65 }
66 break;
67 case '...':
68 if (inFormalParameterList && bracketDepth === 1) {
69 hasRestParam = true;
70 }
71 break;
72 case ')':
73 if (inFormalParameterList && bracketDepth === 1) {
74 break tokenLoop;
75 }
76 // fallthrough
77 case '}': case ']':
78 if (!bracketDepth) {
79 throw new Error(str);
80 }
81 --bracketDepth;
82 break;
83 default:
84 break;
85 }
86 }
87 const record = freeze({
88 __proto__: null,
89 max: declaredParameterCount,
90 usesRest: hasRestParam,
91 });
92 memoTable.set(fun, record);
93 return record;
94}
95
96module.exports = arityOf;
97
98// Per https://github.com/es-shims/es-shim-api :
99
100// require('foo').implementation or require('foo/implementation') is a
101// spec-compliant JS function, that will depend on a receiver
102// (a “this” value) as the spec requires.
103arityOf.implementation = arityOf;
104
105// require('foo').getPolyfill or require('foo/polyfill') is a function
106// that when invoked, will return the most compliant and performant
107// function that it can - if a native version is available, and does
108// not violate the spec, then the native function will be returned -
109// otherwise, either the implementation, or a custom, wrapped version
110// of the native function, will be returned. This is also the result
111// that will be used as the default export.
112arityOf.getPolyfill = function getPolyfill() {
113 // TODO: look for native
114 return arityOf;
115};
116
117// require('foo').shim or require('foo/shim') is a function that when
118// invoked, will call getPolyfill, and if the polyfill doesn’t match
119// the built-in value, will install it into the global environment.
120//
121// The only place the package may modify the environment is within its
122// shim method.
123arityOf.shim = function shim() {
124 if (!hasOwnProperty(prototype, 'maxArity')) {
125 defineProperty(
126 prototype,
127 'maxArity',
128 {
129 configurable: true,
130 get() {
131 return arityOf(this).max;
132 },
133 });
134 }
135 if (!hasOwnProperty(prototype, 'usesRest')) {
136 defineProperty(
137 prototype,
138 'usesRest',
139 {
140 configurable: true,
141 get() {
142 return arityOf(this).usesRest;
143 },
144 });
145 }
146};