Jakie są zasady automatycznego wstawiania średnika (ASI) w JavaScript?

445

Najpierw powinienem chyba zapytać, czy to zależy od przeglądarki.

Czytałem, że jeśli zostanie znaleziony niepoprawny token, ale sekcja kodu jest ważna do tego niepoprawnego tokena, średnik jest wstawiany przed tokenem, jeśli poprzedza go podział linii.

Jednak częstym przykładem cytowanych błędów spowodowanych wstawieniem średnika jest:

return
  _a+b;

.. który nie wydaje się przestrzegać tej zasady, ponieważ _a byłby prawidłowym tokenem.

Z drugiej strony zrywanie łańcuchów połączeń działa zgodnie z oczekiwaniami:

$('#myButton')
  .click(function(){alert("Hello!")});

Czy ktoś ma bardziej szczegółowy opis zasad?

TR
źródło
22
Nie jest Spec ...
Miles
33
@Miles Tylko nie pod twoim zepsutym linkiem ;-) ecma-international.org/publications/standards/Ecma-262.htm
Zach Lysobey
3
Patrz str. 26 wyżej cytowanego pliku PDF.
ᴠɪɴᴄᴇɴᴛ
patrz rozdział 11.9 Automatyczne wstawianie średnika
Andrew Lam,

Odpowiedzi:

454

Przede wszystkim powinieneś wiedzieć, na które instrukcje ma wpływ automatyczne wstawianie średników (zwane również ASI dla zwięzłości):

  • puste oświadczenie
  • var komunikat
  • wyrażenie wyrażenia
  • do-while komunikat
  • continue komunikat
  • break komunikat
  • return komunikat
  • throw komunikat

Konkretne reguły ASI są opisane w specyfikacji § 11.9.1 Zasady automatycznego wstawiania średnika

Opisano trzy przypadki:

  1. Kiedy napotkasz token ( LineTerminatorlub }), który jest niedozwolony przez gramatykę, wstawia się przed nim średnik, jeśli:

    • Token jest oddzielony od poprzedniego tokena co najmniej jednym LineTerminator.
    • Token jest }

    np . :

    { 1
    2 } 3

    przekształca się w

    { 1
    ;2 ;} 3;

    NumericLiteral 1Spełnia pierwszy warunek, następujący znacznik jest terminator linii. Spełnia drugiego warunku, następujący token .
    2}

  2. Gdy napotkany zostanie koniec strumienia wejściowego tokenów, a analizator składni nie będzie w stanie przeanalizować strumienia tokenu wejściowego jako pojedynczego kompletnego Programu, wówczas średnik zostanie automatycznie wstawiony na końcu strumienia wejściowego.

    np . :

    a = b
    ++c

    przekształca się w:

    a = b;
    ++c;
  3. Ten przypadek występuje, gdy token jest dozwolony przez pewną produkcję gramatyki, ale produkcja jest produkcją ograniczoną , średnik jest wstawiany automatycznie przed tokenem ograniczonym.

    Ograniczone produkcje:

    UpdateExpression :
        LeftHandSideExpression [no LineTerminator here] ++
        LeftHandSideExpression [no LineTerminator here] --
    
    ContinueStatement :
        continue ;
        continue [no LineTerminator here] LabelIdentifier ;
    
    BreakStatement :
        break ;
        break [no LineTerminator here] LabelIdentifier ;
    
    ReturnStatement :
        return ;
        return [no LineTerminator here] Expression ;
    
    ThrowStatement :
        throw [no LineTerminator here] Expression ; 
    
    ArrowFunction :
        ArrowParameters [no LineTerminator here] => ConciseBody
    
    YieldExpression :
        yield [no LineTerminator here] * AssignmentExpression
        yield [no LineTerminator here] AssignmentExpression

    Klasyczny przykład z ReturnStatement:

    return 
      "something";

    przekształca się w

    return;
      "something";
CMS
źródło
4
# 1: Token, który nie jest dozwolony przez gramatykę, zwykle nie jest zakończeniem linii, prawda (chyba że masz na myśli ograniczone produkcje z # 3)? Uważa, że ​​należy pominąć nawiasy. # 2 Czy przykład nie powinien pokazywać tylko wstawienia po ++cdla jasności?
Bergi,
3
należy pamiętać, że ASI nie musi tak naprawdę „wstawiać średników”, aby zakończyć instrukcję w parserze silnika ...
Aprillion
1
co to znaczy „strumień wejściowy”, czy to oznacza „linię”? „Strumień tokena wejściowego” utrudnia zrozumienie
nonpolarity
Czy link specyfikacji działa dla kogoś innego? Doprowadziło mnie to do prawie pustej strony z martwym linkiem.
intcreator
wyjaśnij, w jaki sposób, zgodnie z tymi zasadami, poniższy przykład by 者 無極 而 生 z „a [LineBreak] = [LineBreak] 3” nadal działa
Nir O.
45

Prosto z ECMA-262, piąta edycja ECMAScript Specyfikacja :

7.9.1 Zasady automatycznego wstawiania średnika

Istnieją trzy podstawowe zasady wstawiania średnika:

  1. Kiedy, gdy program jest analizowany od lewej do prawej, token (zwany tokenem obrażającym) napotyka ), który nie jest dozwolony przez żadną produkcję gramatyki, wówczas średnik jest wstawiany automatycznie przed tokenem naruszającym, jeśli jeden lub więcej z poniższych warunki są prawdziwe:
    • Obraźliwy token jest oddzielony od poprzedniego tokena przynajmniej jednym LineTerminator .
    • Tokenem obrażającym jest }.
  2. Gdy podczas analizowania programu od lewej do prawej napotyka się koniec strumienia wejściowego tokenów, a analizator składni nie może przeanalizować strumienia tokenu wejściowego jako pojedynczego kompletnego skryptu ECMAScript Program , wówczas średnik jest wstawiany automatycznie na końcu strumień wejściowy.
  3. Gdy podczas analizowania programu od lewej do prawej napotyka się token, który jest dozwolony przez pewną produkcję gramatyki, ale produkcja jest produkcją ograniczoną, a token byłby pierwszym tokenem terminala lub nieterminala bezpośrednio po adnotacji „ [nie LineTerminatortutaj] ” w ramach ograniczonej produkcji (a zatem taki token nazywa się tokenem zastrzeżonym), a token ograniczony jest oddzielony od poprzedniego tokena przez co najmniej jeden LineTerminator , następnie średnik jest wstawiany automatycznie przed tokenem ograniczonym.

Jednak w poprzednich regułach występuje dodatkowy warunek nadrzędny: średnik nigdy nie jest wstawiany automatycznie, jeśli średnik byłby wówczas analizowany jako pusta instrukcja lub jeśli ten średnik stałby się jednym z dwóch średników w nagłówku forinstrukcji (patrz 12.6 .3).

Jörg W Mittag
źródło
44

Nie mogłem zbyt dobrze zrozumieć tych 3 zasad w specyfikacjach - mam nadzieję, że będę mieć coś, co będzie bardziej zrozumiałe po angielsku - ale oto, co zebrałem z JavaScript: The Definitive Guide, 6. wydanie, David Flanagan, O'Reilly, 2011:

Zacytować:

JavaScript nie traktuje każdego podziału linii jako średnika: zwykle traktuje podziały linii jako średniki tylko wtedy, gdy nie może parsować kodu bez średników.

Kolejny cytat: na kod

var a
a
=
3 console.log(a)

JavaScript nie traktuje podziału drugiego wiersza jako średnika, ponieważ może dalej analizować dłuższą instrukcję a = 3;

i:

dwa wyjątki od ogólnej zasady, że JavaScript interpretuje podziały wierszy jako średniki, gdy nie może parsować drugiego wiersza jako kontynuacji instrukcji w pierwszym wierszu. Pierwszy wyjątek dotyczy instrukcji return, break i kontynuuj

... Jeśli po którymkolwiek z tych słów pojawi się podział wiersza ... JavaScript zawsze interpretuje ten podział jako średnik.

... Drugi wyjątek dotyczy operatorów ++ i −− ... Jeśli chcesz użyć jednego z tych operatorów jako operatorów Postfiksa, muszą one pojawiać się w tym samym wierszu, co wyrażenie, którego dotyczą. W przeciwnym razie podział wiersza będzie traktowany jako średnik, a ++ lub - zostanie przeanalizowany jako operator prefiksu zastosowany do następującego kodu. Rozważ ten kod, na przykład:

x 
++ 
y

Jest analizowany jako x; ++y;, a nie jakox++; y

Myślę więc, aby to uprościć, co oznacza:

W ogóle, JavaScript będzie traktować go jako kontynuacji kodu tak długo, jak to ma sens - z wyjątkiem 2 przypadkach: (1) po niektóre słowa kluczowe podoba return, break, continueoraz (2) jeżeli uzna to ++albo --na nowej linii, a następnie doda ;na koniec poprzedniej linii.

Część o „traktuj to jako kontynuację kodu tak długo, jak ma to sens”, sprawia wrażenie chciwego dopasowania wyrażenia regularnego.

W związku z powyższym oznacza to, że returnprzy podziale wiersza interpreter JavaScript wstawi znak;

(cytowany ponownie: jeśli po którymkolwiek z tych słów pojawi się podział wiersza [np. return] ... JavaScript zawsze interpretuje ten podział wiersza jako średnik)

i z tego powodu klasyczny przykład

return
{ 
  foo: 1
}

nie będzie działać zgodnie z oczekiwaniami, ponieważ interpreter JavaScript potraktuje to jako:

return;   // returning nothing
{
  foo: 1
}

Nie może następować łamanie linii natychmiast po return:

return { 
  foo: 1
}

aby działał poprawnie. I możesz wstawić ;siebie, jeśli będziesz przestrzegać zasady używania ;instrukcji po:

return { 
  foo: 1
};
niepolarność
źródło
17

Jeśli chodzi o wstawianie średników i instrukcję var, uważaj, aby nie zapomnieć o przecinku, jeśli używasz zmiennej var, ale obejmuje ona wiele wierszy. Wczoraj ktoś znalazł to w moim kodzie:

    var srcRecords = src.records
        srcIds = [];

Działało, ale efekt był taki, że deklaracja / przypisanie srcIds było globalne, ponieważ lokalna deklaracja z var w poprzednim wierszu nie była już stosowana, ponieważ ta instrukcja została uznana za zakończoną z powodu automatycznego wstawiania średnika.

Dexygen
źródło
4
właśnie dlatego używam jsLint
Zach Lysobey,
1
JsHint / Lint bezpośrednio w edytorze kodu z natychmiastową odpowiedzią :)
dmi3y
5
@balupton Gdy zapomniano przecinka, który zakończyłby linię, automatycznie wstawiany jest średnik. W przeciwieństwie do reguły było to raczej „gotcha”.
Dexygen
1
Myślę, że balupton jest poprawny, różnica polega na tym, że piszesz: var srcRecords = src.records srcIds = [];w jednym wierszu zapominasz przecinek lub piszesz „return a&&b” i nie zapominasz nic ... ale podział linii przed a wstawiałby automatyczny średnik po powrocie, który jest zdefiniowany przez zasady ASI ...
Sebastian
3
Myślę, że przejrzystość pisania var( let, const) w każdym wierszu przewyższa ułamek sekundy potrzebny na wpisanie.
squidbe
5

Najbardziej kontekstowy opis automatycznego wstawiania średników JavaScript, jaki znalazłem, pochodzi z książki o tłumaczach rzemieślniczych .

Dziwna jest zasada „automatycznego wstawiania średników” w JavaScript. Tam, gdzie inne języki zakładają, że większość znaków nowej linii jest znacząca i tylko kilka z nich powinno być ignorowanych w instrukcjach wieloliniowych, JS zakłada coś przeciwnego. Traktuje wszystkie nowe znaki jako bez znaczenia białe znaki, chyba że napotka błąd analizy. Jeśli tak, wraca i próbuje zamienić poprzedni nowy wiersz w średnik, aby uzyskać coś poprawnego gramatycznie.

Następnie opisuje to tak, jakbyś kodował zapach .

Ta notka projektowa zamieniłaby się w diatrybę projektu, gdybym szczegółowo opisał, jak to działa, a tym bardziej na różne sposoby, że jest to zły pomysł. To bałagan. JavaScript jest jedynym językiem, który znam, w którym wiele przewodników po stylach wymaga wyraźnych średników po każdym stwierdzeniu, mimo że teoretycznie pozwala ci to pominąć.

jchook
źródło